PIKApp/plug-ins/gfig/gfig-dobject.c

1079 lines
24 KiB
C

/*
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* This is a plug-in for PIKA.
*
* Generates images containing vector type drawings.
*
* Copyright (C) 1997 Andy Thomas alt@picnic.demon.co.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 <https://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <stdlib.h>
#include <string.h>
#include <libpika/pika.h>
#include <libpika/pikaui.h>
#include "gfig.h"
#include "gfig-dialog.h"
#include "gfig-style.h"
#include "gfig-arc.h"
#include "gfig-bezier.h"
#include "gfig-circle.h"
#include "gfig-dobject.h"
#include "gfig-ellipse.h"
#include "gfig-line.h"
#include "gfig-poly.h"
#include "gfig-rectangle.h"
#include "gfig-spiral.h"
#include "gfig-star.h"
#include "libpika/stdplugins-intl.h"
static GfigObject *operation_obj = NULL;
static GdkPoint *move_all_pnt; /* Point moving all from */
static void draw_one_obj (GfigObject *obj,
cairo_t *cr);
static void do_move_obj (GfigObject *obj,
GdkPoint *to_pnt);
static void do_move_all_obj (GdkPoint *to_pnt);
static void do_move_obj_pnt (GfigObject *obj,
GdkPoint *to_pnt);
static void remove_obj_from_list (GFigObj *obj,
GfigObject *del_obj);
static gint scan_obj_points (DobjPoints *opnt,
GdkPoint *pnt);
void
d_save_object (GfigObject *obj,
GString *string)
{
do_save_obj (obj, string);
switch (obj->type)
{
case BEZIER:
case POLY:
case SPIRAL:
case STAR:
g_string_append_printf (string, "<EXTRA>\n");
g_string_append_printf (string, "%d\n</EXTRA>\n", obj->type_data);
break;
default:
break;
}
}
static DobjType
gfig_read_object_type (gchar *desc)
{
gchar *ptr = desc;
DobjType type;
if (*ptr != '<')
return OBJ_TYPE_NONE;
ptr++;
for (type = LINE; type < NUM_OBJ_TYPES; type++)
{
if (ptr == strstr (ptr, dobj_class[type].name))
return type;
}
return OBJ_TYPE_NONE;
}
GfigObject *
d_load_object (gchar *desc,
FILE *fp)
{
GfigObject *new_obj = NULL;
gint xpnt;
gint ypnt;
gchar buf[MAX_LOAD_LINE];
DobjType type;
type = gfig_read_object_type (desc);
if (type == OBJ_TYPE_NONE)
{
g_message ("Error loading object: type not recognized.");
return NULL;
}
while (get_line (buf, MAX_LOAD_LINE, fp, 0))
{
if (sscanf (buf, "%d %d", &xpnt, &ypnt) != 2)
{
/* Read <EXTRA> block if there is one */
if (!strcmp ("<EXTRA>", buf))
{
if ( !new_obj)
{
g_message ("Error while loading object (no points)");
return NULL;
}
get_line (buf, MAX_LOAD_LINE, fp, 0);
if (sscanf (buf, "%d", &new_obj->type_data) != 1)
{
g_message ("Error while loading object (no type data)");
g_free (new_obj);
return NULL;
}
get_line (buf, MAX_LOAD_LINE, fp, 0);
if (strcmp ("</EXTRA>", buf))
{
g_message ("Syntax error while loading object");
g_free (new_obj);
return NULL;
}
/* Go around and read the last line */
continue;
}
else
return new_obj;
}
if (!new_obj)
new_obj = d_new_object (type, xpnt, ypnt);
else
d_pnt_add_line (new_obj, xpnt, ypnt, -1);
}
return new_obj;
}
GfigObject *
d_new_object (DobjType type,
gint x,
gint y)
{
GfigObject *nobj = g_new0 (GfigObject, 1);
nobj->type = type;
nobj->class = &dobj_class[type];
nobj->points = new_dobjpoint (x, y);
nobj->type_data = 0;
if (type == BEZIER)
{
nobj->type_data = 4;
}
else if (type == POLY)
{
nobj->type_data = 3; /* default to 3 sides */
}
else if (type == SPIRAL)
{
nobj->type_data = 4; /* default to 4 turns */
}
else if (type == STAR)
{
nobj->type_data = 3; /* default to 3 sides 6 points */
}
return nobj;
}
void
gfig_init_object_classes (void)
{
d_arc_object_class_init ();
d_line_object_class_init ();
d_rectangle_object_class_init ();
d_circle_object_class_init ();
d_ellipse_object_class_init ();
d_poly_object_class_init ();
d_spiral_object_class_init ();
d_star_object_class_init ();
d_bezier_object_class_init ();
}
/* Delete a list of points */
void
d_delete_dobjpoints (DobjPoints * pnts)
{
DobjPoints *next;
DobjPoints *pnt2del = pnts;
while (pnt2del)
{
next = pnt2del->next;
g_free (pnt2del);
pnt2del = next;
}
}
DobjPoints *
new_dobjpoint (gint x, gint y)
{
DobjPoints *npnt = g_new0 (DobjPoints, 1);
npnt->pnt.x = x;
npnt->pnt.y = y;
return npnt;
}
DobjPoints *
d_copy_dobjpoints (DobjPoints *pnts)
{
DobjPoints *ret = NULL;
DobjPoints *head = NULL;
DobjPoints *newpnt;
DobjPoints *pnt2copy;
for (pnt2copy = pnts; pnt2copy; pnt2copy = pnt2copy->next)
{
newpnt = new_dobjpoint (pnt2copy->pnt.x, pnt2copy->pnt.y);
if (!ret)
{
head = ret = newpnt;
}
else
{
head->next = newpnt;
head = newpnt;
}
}
return ret;
}
static DobjPoints *
get_diffs (GfigObject *obj,
gint *xdiff,
gint *ydiff,
GdkPoint *to_pnt)
{
DobjPoints *spnt;
g_assert (obj != NULL);
for (spnt = obj->points; spnt; spnt = spnt->next)
{
if (spnt->found_me)
{
*xdiff = spnt->pnt.x - to_pnt->x;
*ydiff = spnt->pnt.y - to_pnt->y;
return spnt;
}
}
return NULL;
}
static gboolean
inside_sqr (GdkPoint *cpnt,
GdkPoint *testpnt)
{
/* Return TRUE if testpnt is near cpnt */
gint x = cpnt->x;
gint y = cpnt->y;
gint tx = testpnt->x;
gint ty = testpnt->y;
return (abs (x - tx) <= SQ_SIZE && abs (y - ty) < SQ_SIZE);
}
static gboolean
scan_obj_points (DobjPoints *opnt,
GdkPoint *pnt)
{
while (opnt)
{
if (inside_sqr (&opnt->pnt, pnt))
{
opnt->found_me = TRUE;
return TRUE;
}
opnt->found_me = FALSE;
opnt = opnt->next;
}
return FALSE;
}
static GfigObject *
get_nearest_objs (GFigObj *obj,
GdkPoint *pnt)
{
/* Nearest object to given point or NULL */
GList *all;
GfigObject *test_obj;
gint count = 0;
if (!obj)
return NULL;
for (all = obj->obj_list; all; all = g_list_next (all))
{
test_obj = all->data;
if (count == obj_show_single || obj_show_single == -1)
if (scan_obj_points (test_obj->points, pnt))
{
return test_obj;
}
count++;
}
return NULL;
}
void
object_operation_start (PikaGfig *gfig,
GdkPoint *pnt,
gboolean shift_down)
{
GfigObject *new_obj;
/* Find point in given object list */
operation_obj = get_nearest_objs (gfig_context->current_obj, pnt);
/* Special case if shift down && move obj then moving all objs */
if (shift_down && selvals.otype == MOVE_OBJ)
{
move_all_pnt = g_new (GdkPoint, 1);
*move_all_pnt = *pnt; /* Structure copy */
setup_undo (gfig);
return;
}
if (!operation_obj)
return;/* None to work on */
gfig_context->selected_obj = operation_obj;
setup_undo (gfig);
switch (selvals.otype)
{
case MOVE_OBJ:
if (operation_obj->type == BEZIER)
{
tmp_bezier = operation_obj;
}
break;
case MOVE_POINT:
if (operation_obj->type == BEZIER)
{
tmp_bezier = operation_obj;
}
/* If shift is down the break into sep lines */
if ((operation_obj->type == POLY
|| operation_obj->type == STAR)
&& shift_down)
{
switch (operation_obj->type)
{
case POLY:
d_poly2lines (operation_obj);
break;
case STAR:
d_star2lines (operation_obj);
break;
default:
break;
}
/* Re calc which object point we are looking at */
scan_obj_points (operation_obj->points, pnt);
gtk_widget_queue_draw (gfig_context->preview);
}
break;
case COPY_OBJ:
/* Copy the "operation object" */
/* Then bung us into "copy/move" mode */
new_obj = (GfigObject*) operation_obj->class->copyfunc (operation_obj);
if (new_obj)
{
gfig_style_copy (&new_obj->style, &operation_obj->style, "Object");
scan_obj_points (new_obj->points, pnt);
add_to_all_obj (gfig, gfig_context->current_obj, new_obj);
operation_obj = new_obj;
selvals.otype = MOVE_COPY_OBJ;
gtk_widget_queue_draw (gfig_context->preview);
}
break;
case DEL_OBJ:
remove_obj_from_list (gfig_context->current_obj, operation_obj);
break;
case SELECT_OBJ:
/* don't need to do anything */
break;
case MOVE_COPY_OBJ: /* Never when button down */
default:
g_warning ("Internal error selvals.otype object operation start");
break;
}
}
void
object_operation_end (GdkPoint *pnt,
gboolean shift_down)
{
if (selvals.otype != DEL_OBJ && operation_obj &&
operation_obj->type == BEZIER)
{
tmp_bezier = NULL; /* use as switch */
}
if (operation_obj && selvals.otype != DEL_OBJ)
gfig_style_set_context_from_style (&operation_obj->style);
operation_obj = NULL;
if (move_all_pnt)
{
g_free (move_all_pnt);
move_all_pnt = NULL;
}
/* Special case - if copying mode MUST be copy when button up received */
if (selvals.otype == MOVE_COPY_OBJ)
selvals.otype = COPY_OBJ;
}
/* Move object around */
void
object_operation (GdkPoint *to_pnt,
gboolean shift_down)
{
/* Must do different things depending on object type */
/* but must have object to operate on! */
/* Special case - if shift own and move_obj then move ALL objects */
if (move_all_pnt && shift_down && selvals.otype == MOVE_OBJ)
{
do_move_all_obj (to_pnt);
return;
}
if (!operation_obj)
return;
switch (selvals.otype)
{
case MOVE_OBJ:
case MOVE_COPY_OBJ:
switch (operation_obj->type)
{
case LINE:
case RECTANGLE:
case CIRCLE:
case ELLIPSE:
case POLY:
case ARC:
case STAR:
case SPIRAL:
case BEZIER:
do_move_obj (operation_obj, to_pnt);
break;
default:
/* Internal error */
g_warning ("Internal error in operation_obj->type");
break;
}
break;
case MOVE_POINT:
switch (operation_obj->type)
{
case LINE:
case RECTANGLE:
case CIRCLE:
case ELLIPSE:
case POLY:
case ARC:
case STAR:
case SPIRAL:
case BEZIER:
do_move_obj_pnt (operation_obj, to_pnt);
break;
default:
/* Internal error */
g_warning ("Internal error in operation_obj->type");
break;
}
break;
case DEL_OBJ:
case SELECT_OBJ:
break;
case COPY_OBJ: /* Should have been changed to MOVE_COPY_OBJ */
default:
g_warning ("Internal error selvals.otype");
break;
}
}
static void
update_pnts (GfigObject *obj,
gint xdiff,
gint ydiff)
{
DobjPoints *spnt;
g_assert (obj != NULL);
/* Update all pnts */
for (spnt = obj->points; spnt; spnt = spnt->next)
{
spnt->pnt.x -= xdiff;
spnt->pnt.y -= ydiff;
}
}
static void
remove_obj_from_list (GFigObj *obj,
GfigObject *del_obj)
{
/* Nearest object to given point or NULL */
g_assert (del_obj != NULL);
if (g_list_find (obj->obj_list, del_obj))
{
obj->obj_list = g_list_remove (obj->obj_list, del_obj);
free_one_obj (del_obj);
if (obj->obj_list)
gfig_context->selected_obj = obj->obj_list->data;
else
gfig_context->selected_obj = NULL;
if (obj_show_single != -1)
{
/* We've just deleted the only visible one */
draw_grid_clear ();
obj_show_single = -1; /* Show entry again */
}
gtk_widget_queue_draw (gfig_context->preview);
}
else
g_warning (_("Hey, where has the object gone?"));
}
static void
do_move_all_obj (GdkPoint *to_pnt)
{
/* Move all objects in one go */
/* Undraw/then draw in new pos */
gint xdiff = move_all_pnt->x - to_pnt->x;
gint ydiff = move_all_pnt->y - to_pnt->y;
if (xdiff || ydiff)
{
GList *all;
for (all = gfig_context->current_obj->obj_list; all; all = all->next)
{
GfigObject *obj = all->data;
update_pnts (obj, xdiff, ydiff);
}
*move_all_pnt = *to_pnt;
gtk_widget_queue_draw (gfig_context->preview);
}
}
void
do_save_obj (GfigObject *obj,
GString *string)
{
DobjPoints *spnt;
for (spnt = obj->points; spnt; spnt = spnt->next)
{
g_string_append_printf (string, "%d %d\n", spnt->pnt.x, spnt->pnt.y);
}
}
static void
do_move_obj (GfigObject *obj,
GdkPoint *to_pnt)
{
/* Move the whole line - undraw the line to start with */
/* Then draw in new pos */
gint xdiff = 0;
gint ydiff = 0;
get_diffs (obj, &xdiff, &ydiff, to_pnt);
if (xdiff || ydiff)
{
update_pnts (obj, xdiff, ydiff);
gtk_widget_queue_draw (gfig_context->preview);
}
}
static void
do_move_obj_pnt (GfigObject *obj,
GdkPoint *to_pnt)
{
/* Move the whole line - undraw the line to start with */
/* Then draw in new pos */
DobjPoints *spnt;
gint xdiff = 0;
gint ydiff = 0;
spnt = get_diffs (obj, &xdiff, &ydiff, to_pnt);
if ((!xdiff && !ydiff) || !spnt)
return;
spnt->pnt.x = spnt->pnt.x - xdiff;
spnt->pnt.y = spnt->pnt.y - ydiff;
/* Draw in new pos */
gtk_widget_queue_draw (gfig_context->preview);
}
/* copy objs */
GList *
copy_all_objs (GList *objs)
{
GList *new_all_objs = NULL;
while (objs)
{
GfigObject *object = objs->data;
GfigObject *new_object = (GfigObject *) object->class->copyfunc (object);
gfig_style_copy (&new_object->style, &object->style, "Object");
new_all_objs = g_list_prepend (new_all_objs, new_object);
objs = objs->next;
}
new_all_objs = g_list_reverse (new_all_objs);
return new_all_objs;
}
/* Screen refresh */
static void
draw_one_obj (GfigObject *obj,
cairo_t *cr)
{
obj->class->drawfunc (obj, cr);
}
void
draw_objects (GList *objs,
gboolean show_single,
cairo_t *cr)
{
/* Show_single - only one object to draw Unless shift
* is down in which case show all.
*/
gint count = 0;
while (objs)
{
if (!show_single || count == obj_show_single || obj_show_single == -1)
draw_one_obj (objs->data, cr);
objs = g_list_next (objs);
count++;
}
}
void
prepend_to_all_obj (PikaGfig *gfig,
GFigObj *fobj,
GList *nobj)
{
setup_undo (gfig); /* Remember ME */
fobj->obj_list = g_list_concat (fobj->obj_list, nobj);
}
static void
scale_obj_points (DobjPoints *opnt,
gdouble scale_x,
gdouble scale_y)
{
while (opnt)
{
opnt->pnt.x = (gint) (opnt->pnt.x * scale_x);
opnt->pnt.y = (gint) (opnt->pnt.y * scale_y);
opnt = opnt->next;
}
}
void
add_to_all_obj (PikaGfig *gfig,
GFigObj *fobj,
GfigObject *obj)
{
GList *nobj = NULL;
nobj = g_list_append (nobj, obj);
if (need_to_scale)
scale_obj_points (obj->points, scale_x_factor, scale_y_factor);
prepend_to_all_obj (gfig, fobj, nobj);
/* initialize style when we add the object */
gfig_context->selected_obj = obj;
}
/* First button press -- start drawing object */
/*
* object_start() creates a new object of the type specified in the
* button panel. It is activated by a button press, and causes
* a small square to be drawn at the initial point. The style of
* the new object is set to values taken from the style control
* widgets.
*/
void
object_start (GdkPoint *pnt,
gboolean shift_down)
{
/* start for the current object */
if (!selvals.scaletoimage)
{
need_to_scale = 1;
selvals.scaletoimage = 1;
}
else
{
need_to_scale = 0;
}
switch (selvals.otype)
{
case LINE:
/* Shift means we are still drawing */
d_line_start (pnt, shift_down);
break;
case RECTANGLE:
d_rectangle_start (pnt, shift_down);
break;
case CIRCLE:
d_circle_start (pnt, shift_down);
break;
case ELLIPSE:
d_ellipse_start (pnt, shift_down);
break;
case POLY:
d_poly_start (pnt, shift_down);
break;
case ARC:
d_arc_start (pnt, shift_down);
break;
case STAR:
d_star_start (pnt, shift_down);
break;
case SPIRAL:
d_spiral_start (pnt, shift_down);
break;
case BEZIER:
d_bezier_start (pnt, shift_down);
break;
default:
/* Internal error */
break;
}
if (obj_creating)
{
if (gfig_context->debug_styles)
g_printerr ("Creating object, setting style from context\n");
gfig_style_set_style_from_context (&obj_creating->style);
}
}
void
object_end (PikaGfig *gfig,
GdkPoint *pnt,
gboolean shift_down)
{
/* end for the current object */
/* Add onto global object list */
/* If shift is down may carry on drawing */
switch (selvals.otype)
{
case LINE:
d_line_end (gfig, pnt, shift_down);
break;
case RECTANGLE:
d_rectangle_end (gfig, pnt, shift_down);
break;
case CIRCLE:
d_circle_end (gfig, pnt, shift_down);
break;
case ELLIPSE:
d_ellipse_end (gfig, pnt, shift_down);
break;
case POLY:
d_poly_end (gfig, pnt, shift_down);
break;
case STAR:
d_star_end (gfig, pnt, shift_down);
break;
case ARC:
d_arc_end (gfig, pnt, shift_down);
break;
case SPIRAL:
d_spiral_end (gfig, pnt, shift_down);
break;
case BEZIER:
d_bezier_end (gfig, pnt, shift_down);
break;
default:
/* Internal error */
break;
}
if (need_to_scale)
{
need_to_scale = 0;
selvals.scaletoimage = 0;
}
}
/* Stuff for the generation/deletion of objects. */
/* Objects are easy one they are created - you just go down the object
* list calling the draw function for each object but... when they
* are been created we have to be a little more careful. When
* the first point is placed on the canvas we create the object,
* the mouse position then defines the next point that can move around.
* careful how we draw this position.
*/
void
free_one_obj (GfigObject *obj)
{
d_delete_dobjpoints (obj->points);
g_free (obj);
}
void
free_all_objs (GList *objs)
{
g_list_free_full (objs, (GDestroyNotify) free_one_obj);
}
gchar *
get_line (gchar *buf,
gint s,
FILE *from,
gint init)
{
gint slen;
char *ret;
if (init)
line_no = 1;
else
line_no++;
do
{
ret = fgets (buf, s, from);
}
while (!ferror (from) && buf[0] == '#');
slen = strlen (buf);
/* The last newline is a pain */
if (slen > 0)
buf[slen - 1] = '\0';
/* Check and remove an '\r' too from Windows */
if ((slen > 1) && (buf[slen - 2] == '\r'))
buf[slen - 2] = '\0';
if (ferror (from))
{
g_warning (_("Error reading file"));
return NULL;
}
#ifdef DEBUG
printf ("Processing line '%s'\n", buf);
#endif /* DEBUG */
return ret;
}
void
clear_undo (PikaGfig *gfig)
{
int lv;
GAction *action;
for (lv = undo_level; lv >= 0; lv--)
{
free_all_objs (undo_table[lv]);
undo_table[lv] = NULL;
}
undo_level = -1;
action = g_action_map_lookup_action (G_ACTION_MAP (gfig->app), "undo");
g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
FALSE);
}
void
setup_undo (PikaGfig *gfig)
{
GAction *action;
/* Copy object list to undo buffer */
#if DEBUG
printf ("setup undo level [%d]\n", undo_level);
#endif /*DEBUG*/
if (!gfig_context->current_obj)
{
/* If no current_obj must be loading -> no undo */
return;
}
if (undo_level >= selvals.maxundo - 1)
{
int loop;
/* the little one in the bed said "roll over".. */
if (undo_table[0])
free_one_obj (undo_table[0]->data);
for (loop = 0; loop < undo_level; loop++)
{
undo_table[loop] = undo_table[loop + 1];
}
}
else
{
undo_level++;
}
undo_table[undo_level] =
copy_all_objs (gfig_context->current_obj->obj_list);
action = g_action_map_lookup_action (G_ACTION_MAP (gfig->app), "undo");
g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE);
gfig_context->current_obj->obj_status |= GFIG_MODIFIED;
}
void
new_obj_2edit (PikaGfig *gfig,
GFigObj *obj)
{
GAction *action;
GFigObj *old_current = gfig_context->current_obj;
/* Clear undo levels */
/* redraw the preview */
/* Set up options as define in the selected object */
clear_undo (gfig);
/* Point at this one */
gfig_context->current_obj = obj;
/* Show all objects to start with */
obj_show_single = -1;
/* Change options */
options_update (old_current);
/* redraw with new */
gtk_widget_queue_draw (gfig_context->preview);
action = g_action_map_lookup_action (G_ACTION_MAP (gfig->app), "save");
if (obj->obj_status & GFIG_READONLY)
{
g_message (_("Editing read-only object - "
"you will not be able to save it"));
g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
FALSE);
}
else
{
g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
FALSE);
}
}
/* Add a point to a line (given x, y)
* pos = 0 = head
* pos = -1 = tail
* 0 < pos = nth position
*/
void
d_pnt_add_line (GfigObject *obj,
gint x,
gint y,
gint pos)
{
DobjPoints *npnts = new_dobjpoint (x, y);
g_assert (obj != NULL);
if (!pos)
{
/* Add to head */
npnts->next = obj->points;
obj->points = npnts;
}
else
{
DobjPoints *pnt = obj->points;
/* Go down chain until the end if pos */
while (pos < 0 || pos-- > 0)
{
if (!(pnt->next) || !pos)
{
npnts->next = pnt->next;
pnt->next = npnts;
break;
}
else
{
pnt = pnt->next;
}
}
}
}