home *** CD-ROM | disk | FTP | other *** search
/ The Fred Fish Collection 1.5 / ffcollection-1-5-1992-11.iso / ff_disks / 200-299 / ff280.lzh / Graph / grph.c < prev    next >
C/C++ Source or Header  |  1989-11-20  |  55KB  |  1,825 lines

  1. /*
  2.  *                 GRAPH, Version 1.00 - 4 August 1989
  3.  *
  4.  *            Copyright 1989, David Gay. All Rights Reserved.
  5.  *            This software is freely redistrubatable.
  6.  */
  7.  
  8. /* Graph manipulation */
  9.  
  10. #include <exec/types.h>
  11. #include <graphics/gfxbase.h>
  12. #include <graphics/rastport.h>
  13. #include <intuition/intuition.h>
  14. #include <devices/prtbase.h>
  15. #include <devices/printer.h>
  16. #include <stdio.h>
  17. #include <string.h>
  18. #include <math.h>
  19. #include <limits.h>
  20. #include <iff/iff.h>
  21. #include <iff/ilbm.h>
  22.  
  23. #include "grph.h"
  24. #include "file.h"
  25. #include "graphics.h"
  26. #include "graph.h"
  27. #include "uio.h"
  28. #include "object.h"
  29. #include "list.h"
  30. #include "coords.h"
  31. #include "user/gadgets.h"
  32. #include "tracker.h"
  33.  
  34. #include <proto/exec.h>
  35. #include <proto/graphics.h>
  36. #include <proto/diskfont.h>
  37. #include <proto/intuition.h>
  38.  
  39. #define INCH 2.54e-2
  40. #define DIGHEIGHT 8   /* Digit font size, in points (1 point = 1/72 inch) */
  41. #define XTICK 0.05    /* Size of tick for X axis, In inches */
  42. #define YTICK 0.05
  43. #define XTEXT 0.12
  44. #define YTEXT 0.09
  45.  
  46. #define MAXPRINTPAGES 1 /* Max length for printing */
  47. #define CHOOSELIST 2    /* for choose req */
  48. #define IFFDISK 2       /* For iff req */
  49.  
  50. /* A function to send a slice of output to the chosen device (disk/file) */
  51. typedef int (*prtfunc)(struct graph *g, struct RastPort *rp, int w, int h, int
  52. y, int slice, struct Requester *abreq);
  53.  
  54. extern struct GfxBase *GfxBase;
  55.  
  56. static UWORD scr_dpmx, scr_dpmy; /* The screen 'resolution' */
  57.  
  58. /* Data for printer output */
  59. static struct IODRPReq *prtio;
  60. static struct MsgPort *prtport;
  61. static struct PrinterExtendedData *ped;
  62. static struct Preferences *prtprefs;
  63. static struct ColorMap *prtcm;
  64.  
  65. /* Data for iff output */
  66. static FILE *iff_file;
  67. static long form_pos, body_pos;
  68. static char iff_filename[FILELEN];
  69. static struct Gadget *iffg;
  70.  
  71. static char *prt_error[] = {
  72.     "Unknown error",
  73.     "Print cancelled",
  74.     "Not a graphics printer !",
  75.     "Obsolete",
  76.     "Illegal print dimensions",
  77.     "Obsolete",
  78.     "No memory (internal)",
  79.     "No memory (for buffers)"
  80. };
  81.  
  82. #define MAX_PRT_ERROR (sizeof(prt_error) / sizeof(char *) - 1)
  83.  
  84. /* Check that ax can be displayed */
  85. static int ax_ok(const struct ax *a, const struct ax *other)
  86. {
  87.     return (a->ax == NOVAL ||
  88.              ((!other->log || a->ax > 0.0) &&
  89.                (a->cstep == NOVAL ||
  90.                  (a->cstep > 0.0 &&
  91.                    (a->every == INOVAL || a->every > 0)))));
  92. }
  93.  
  94. /* Check that ax has legal values */
  95. static int ax_ok2(const struct ax *a)
  96. {
  97.     return a->min != NOVAL && a->max != NOVAL && a->min < a->max &&
  98.            (!a->log || a->min > 0.0);
  99. }
  100.  
  101. /* Check if graph is displayable */
  102. static int graph_ok(struct graph *g)
  103. {
  104.     g->a.ok = ax_ok(&g->a.x, &g->a.y) && ax_ok(&g->a.y, &g->a.x) &&
  105.               (g->a.ratio == NOVAL || g->a.ratio > 0.0);
  106.  
  107.     return ax_ok2(&g->a.x) && ax_ok2(&g->a.x);
  108. }
  109.  
  110. /* Redraw graph after changes */
  111. static void check_graph(struct graph *g)
  112. {
  113.     g->saved = FALSE;
  114.     g->ok = graph_ok(g);
  115.     set_scale(g);
  116.     draw_graph(g, TRUE);
  117. }
  118.  
  119. /* Find object in graph by name */
  120. static struct object *find_object(struct graph *g, char *name)
  121. {
  122.     struct object *o;
  123.  
  124.     for (o = first(&g->o_list); succ(o); o = succ(o))
  125.         if (strcmp(o->name, name) == 0) return o;
  126.  
  127.     return NULL;
  128. }
  129.  
  130. /* Convert inches to rastport dots, using current dpm */
  131. /* Use integer arithmetic if needs to be called often */
  132. int xinch2dots(struct graph *g, double x)
  133. {
  134.     return (int)(x * INCH * g->io.dpmx + 0.5);
  135. }
  136.  
  137. int yinch2dots(struct graph *g, double y)
  138. {
  139.     return (int)(y * INCH * g->io.dpmy + 0.5);
  140. }
  141.  
  142. /* Open font in pts points */
  143. struct TextFont *open_font(struct graph *g, char *name, int pts, int style, int
  144.  flags)
  145. {
  146.     struct TextAttr ta;
  147.     struct TextFont *tf1, *tf2;
  148.  
  149.     ta.ta_Name = name;
  150.     ta.ta_YSize = yinch2dots(g, pts / 72.0);
  151.     ta.ta_Style = style;
  152.     ta.ta_Flags = flags;
  153.  
  154.     tf1 = OpenFont(&ta);
  155.     if (!tf1)
  156.         return OpenDiskFont(&ta);
  157.     else if (tf1->tf_YSize != ta.ta_YSize)
  158.     {
  159.         tf2 = OpenDiskFont(&ta);
  160.  
  161.         if (tf2)
  162.         {
  163.             CloseFont(tf1);
  164.             return tf2;
  165.         }
  166.         else
  167.             return tf2;
  168.     }
  169.     else
  170.         return tf1;
  171. }
  172.  
  173. /* add object to graph (object is already displayed) */
  174. struct object *add_object(struct graph *g, struct object *o)
  175. {
  176.     if (o)
  177.     {
  178.         if (o->name[0] != '\0' && find_object(g, o->name))
  179.         {
  180.             message(g, "Name already used", (char *)NULL);
  181.             refresh_graph(g, TRUE, o->delete(o));
  182.             return NULL;
  183.         }
  184.         add_head(&g->o_list, o);
  185.         g->saved = FALSE;
  186.     }
  187.     return o;
  188. }
  189.  
  190. /* Remove object & redisplay graph */
  191. void remove_object(struct graph *g, struct object *o)
  192. {
  193.     if (o == g->s.current)
  194.     {
  195.         o->deselect(o);
  196.         g->s.current = NULL;
  197.         disable_rect_menus(g);
  198.         disable_object_menus(g);
  199.     }
  200.     remove(o);
  201.     g->saved = FALSE;
  202.     refresh_graph(g, TRUE, o->delete(o));
  203. }
  204.  
  205. /* Make object o selected */
  206. void select_object(struct graph *g, struct object *o)
  207. {
  208.     if (o)
  209.     {
  210.         g->s.current = o;
  211.         o->select(o); /* Inform object of this */
  212.         enable_object_menus(g);
  213.         set_title(g);
  214.     }
  215. }
  216.  
  217. /* Deslect current object */
  218. void deselect(struct graph *g)
  219. {
  220.     struct Region *ref;
  221.  
  222.     /* Deselect object */
  223.     ref = g->s.current ? g->s.current->deselect(g->s.current) : NULL;
  224.     g->s.current = NULL;
  225.  
  226.     disable_rect_menus(g);
  227.     disable_object_menus(g);
  228.     refresh_graph(g, TRUE, ref);
  229. }
  230.  
  231. /* User pressed mouse button */
  232. void mouse_down(struct graph *g, WORD sx, WORD sy)
  233. {
  234.     if (g->ok && g->io.rw)
  235.     {
  236.         /* Get real pos */
  237.         g->s.x = g->io.rw->x(g->io.rw, sx);
  238.         g->s.y = g->io.rw->y(g->io.rw, sy);
  239.         /* If nothing selected, or if not clicking in selected object */
  240.         if (!g->s.current || !g->s.current->down(g->s.current))
  241.         {
  242.             deselect(g);
  243.  
  244.             if (g->s.select_mode) /* Try to select something */
  245.             {
  246.                 struct object *o;
  247.  
  248.                 for (o = first(&g->o_list); succ(o); o = succ(o))
  249.                     if (o->down(o)) /* Inside object ? */
  250.                     {
  251.                         select_object(g, o);
  252.                         break; /* exit for loop */
  253.                     }
  254.             }
  255.             else /* Start a new rectangle */
  256.                 if (g->s.current = (struct object *)new_pos(g))
  257.                     enable_rect_menus(g);
  258.  
  259.         }
  260.         if (g->s.current) /* Something is now selected, keep track of mouse */
  261.         {
  262.             /* Adjust position for offset in object */
  263.             g->s.x = g->io.rw->x(g->io.rw, sx - g->s.current->mx);
  264.             g->s.y = g->io.rw->y(g->io.rw, sy - g->s.current->my);
  265.             ReportMouse(g->io.win, TRUE);
  266.             g->s.mouse = TRUE;
  267.         }
  268.         set_title(g);
  269.     }
  270. }
  271.  
  272. /* Mouse has moved */
  273. void mouse_move(struct graph *g, WORD sx, WORD sy)
  274. {
  275.     if (g->s.mouse)
  276.     {
  277.         /* Adjust for offset, calc pos in coord system */
  278.         sx -= g->s.current->mx;
  279.         sy -= g->s.current->my;
  280.         g->s.x = g->io.rw->x(g->io.rw, sx);
  281.         g->s.y = g->io.rw->y(g->io.rw, sy);
  282.  
  283.         /* Inform selection of movement */
  284.         g->s.current->move(g->s.current);
  285.  
  286.         set_title(g);
  287.     }
  288. }
  289.  
  290. /* Mouse button released */
  291. void mouse_up(struct graph *g, WORD sx, WORD sy)
  292. {
  293.     if (g->s.mouse) /* mouse was down */
  294.     {
  295.         g->saved = FALSE;  ./* Graph has very probably changed */
  296.         g->s.mouse = FALSE;
  297.  
  298.         sx -= g->s.current->mx;
  299.         sy -= g->s.current->my;
  300.         g->s.x = g->io.rw->x(g->io.rw, sx);
  301.         g->s.y = g->io.rw->y(g->io.rw, sy);
  302.  
  303.         ReportMouse(g->io.win, FALSE);
  304.         /* Redaw whatever is necessary */
  305.         refresh_graph(g, TRUE, g->s.current->up(g->s.current));
  306.     }
  307. }
  308.  
  309. /* Handler for object selection requester */
  310. static int choose_handler(struct Gadget *gg, ULONG class, struct Requester *req
  311. , struct graph *g)
  312. {
  313.     if (gg->GadgetID == CHOOSELIST) /* In a list */
  314.     {
  315.         if (ModifyList(gg, req, req->RWindow, class == GADGETUP) == 2)
  316.         {
  317.             EndRequest(req, req->RWindow);
  318.             return TRUE;
  319.         }
  320.     }
  321.     else return std_ghandler(gg, class, req, g);
  322. }
  323.  
  324. /* Ask user to choose an object, by name */
  325. struct object *choose_object(struct graph *g, char *op)
  326. {
  327.     struct object *o, *sel = NULL;
  328.     char name[FNAMELEN];
  329.     tlist l;
  330.     char what[30];
  331.     int ok;
  332.  
  333.     /* Construct title */
  334.     what[29] = '\0'; name[0] = '\0';
  335.     strncpy(what, op, 29);
  336.     strncat(what, " function", 29 - strlen(what));
  337.  
  338.     /* Construct list of named objects */
  339.     new_list(&l);
  340.     ok = TRUE;
  341.     for (o = first(&g->o_list); succ(o); o = succ(o))
  342.         if (o->name[0] != '\0')
  343.         {
  344.             tnode *n = alloc_node(sizeof(tnode));
  345.  
  346.             if (!n)
  347.             {
  348.                 ok = FALSE;
  349.                 break;
  350.             }
  351.             n->ln_Name = o->name;
  352.             add_tail(&l, n);
  353.         }
  354.  
  355.     if (ok) /* list constructed ok */
  356.     {
  357.         /* Create requester */
  358.         struct Requester *req;
  359.         struct Memory *m;
  360.         struct Gadget *gl = NULL;
  361.  
  362.         if ((m = NewMemory()) &&
  363.             (req = InitReq(50, 20, 200, 120, m)) &&
  364.             SetReqBorder(req, 1, m) &&
  365.             AddIntuiText(&req->ReqText, what, 100 - 4 * strlen(what), 6, m) &&
  366.             AddList(&gl, CHOOSELIST, "Name", &l, name, FNAMELEN, 0, RELVERIFY |
  367.  ENDGADGET, 20, 20, 160, 80, TRUE, m) &&
  368.             AddBox(&gl, TRUE, "Ok", 0, RELVERIFY | ENDGADGET, 18, 95, 65, 15, F
  369. ALSE, m) &&
  370.             AddBox(&gl, FALSE, "Cancel", 0, RELVERIFY | ENDGADGET, 118, 95, 65,
  371.  15, FALSE, m))
  372.         {
  373.             SetReqGadgets(req, gl);
  374.             if (DoRequest(req, g, choose_handler))
  375.             {
  376.                 /* Remove blanks */
  377.                 strip(name);
  378.                 if (*name)
  379.                 {
  380.                     sel = find_object(g, name);
  381.                     if (!sel) message(g, "No such function", (char *)NULL);
  382.                 }
  383.             }
  384.         }
  385.         Free(m);
  386.     }
  387.     free_list((list *)&l, sizeof(tnode));
  388.     return sel;
  389. }
  390.  
  391. /* Define coordinate system */
  392. void enter_limits(struct graph *g)
  393. {
  394.     struct Requester *req;
  395.     struct Memory *m;
  396.     struct Gadget *gl = NULL, *x_log, *y_log;
  397.     char xmin[NBLEN], xmax[NBLEN], ymin[NBLEN], ymax[NBLEN], ratio[NBLEN];
  398.  
  399.     double2str(xmin, g->a.x.min);
  400.     double2str(xmax, g->a.x.max);
  401.     double2str(ymin, g->a.y.min);
  402.     double2str(ymax, g->a.y.max);
  403.     double2str(ratio, g->a.ratio);
  404.  
  405.     if ((m = NewMemory()) &&
  406.         (req = InitReq(50, 20, 325, 105, m)) &&
  407.         SetReqBorder(req, 1, m) &&
  408.         AddIntuiText(&req->ReqText, "Limits", 138, 6, m) &&
  409.         AddText(&gl, 0, "X: Min ", FALSE, xmin, NBLEN, TRUE, 0, RELVERIFY, 67,
  410. 20, 80, 10, TRUE, m) &&
  411.         AddText(&gl, 0, "Max ", FALSE, xmax, NBLEN, TRUE, 0, RELVERIFY, 190, 20
  412. , 80, 10, TRUE, m) &&
  413.         (x_log = AddOption(&gl, 0, "Log", FALSE, g->a.x.log * SELECTED, 0, 305,
  414.  20, 10, 10, m)) &&
  415.         AddText(&gl, 0, "Y: Min ", FALSE, ymin, NBLEN, TRUE, 0, RELVERIFY, 67,
  416. 40, 80, 10, TRUE, m) &&
  417.         AddText(&gl, 0, "Max ", FALSE, ymax, NBLEN, TRUE, 0, RELVERIFY, 190, 40
  418. , 80, 10, TRUE, m) &&
  419.         (y_log = AddOption(&gl, 0, "Log", FALSE, g->a.y.log * SELECTED, 0, 305,
  420.  40, 10, 10, m)) &&
  421.         AddText(&gl, 0, "Ratio (Y/X) ", FALSE, ratio, NBLEN, TRUE, 0, RELVERIFY
  422. , 107, 60, 80, 10, TRUE, m) &&
  423.         AddBox(&gl, TRUE, "Ok", 0, RELVERIFY | ENDGADGET, 49, 80, 65, 15, FALSE
  424. , m) &&
  425.         AddBox(&gl, FALSE, "Cancel", 0, RELVERIFY | ENDGADGET, 211, 80, 65, 15,
  426.  FALSE, m))
  427.     {
  428.         SetReqGadgets(req, gl);
  429.         if (DoRequest(req, g, std_ghandler))
  430.         {
  431.             g->a.x.min = str2double(xmin);
  432.             g->a.x.max = str2double(xmax);
  433.             g->a.x.log = (x_log->Flags & SELECTED) != 0;
  434.             g->a.y.min = str2double(ymin);
  435.             g->a.y.max = str2double(ymax);
  436.             g->a.y.log = (y_log->Flags & SELECTED) != 0;
  437.             g->a.ratio = str2double(ratio);
  438.             check_graph(g);
  439.         }
  440.     }
  441.     Free(m);
  442. }
  443.  
  444. /* Choose axes display options */
  445. void enter_axes(struct graph *g)
  446. {
  447.     struct Requester *req;
  448.     struct Memory *m;
  449.     struct Gadget *gl = NULL;
  450.     char ax_x[NBLEN], ax_y[NBLEN], step_x[NBLEN], step_y[NBLEN],
  451.          every_x[INTLEN], every_y[INTLEN];
  452.  
  453.     double2str(ax_x, g->a.x.ax);
  454.     double2str(step_x, g->a.x.cstep);
  455.     int2str(every_x, g->a.x.every);
  456.     double2str(ax_y, g->a.y.ax);
  457.     double2str(step_y, g->a.y.cstep);
  458.     int2str(every_y, g->a.y.every);
  459.  
  460.     if ((m = NewMemory()) &&
  461.         (req = InitReq(50, 15, 225, 165, m)) &&
  462.         SetReqBorder(req, 1, m) &&
  463.         AddIntuiText(&req->ReqText, "Axes", 96, 6, m) &&
  464.         AddText(&gl, 0, "X: Axe at ", FALSE, ax_x, NBLEN, TRUE, 0, RELVERIFY, 9
  465. 1, 20, 80, 10, TRUE, m) &&
  466.         AddText(&gl, 0, "   Ticks every ", FALSE, step_x, NBLEN, TRUE, 0, RELVE
  467. RIFY, 131, 40, 80, 10, TRUE, m) &&
  468.         AddText(&gl, 0, "   numbered every ", FALSE, every_x, INTLEN, TRUE, 0,
  469. RELVERIFY, 155, 60, 32, 10, TRUE, m) &&
  470.         AddText(&gl, 0, "Y: Axe at ", FALSE, ax_y, NBLEN, TRUE, 0, RELVERIFY, 9
  471. 1, 80, 80, 10, TRUE, m) &&
  472.         AddText(&gl, 0, "   Ticks every ", FALSE, step_y, NBLEN, TRUE, 0, RELVE
  473. RIFY, 131, 100, 80, 10, TRUE, m) &&
  474.         AddText(&gl, 0, "   numbered every ", FALSE, every_y, INTLEN, TRUE, 0,
  475. RELVERIFY, 155, 120, 32, 10, TRUE, m) &&
  476.         AddBox(&gl, TRUE, "Ok", 0, RELVERIFY | ENDGADGET, 24, 140, 65, 15, FALS
  477. E, m) &&
  478.         AddBox(&gl, FALSE, "Cancel", 0, RELVERIFY | ENDGADGET, 136, 140, 65, 15
  479. , FALSE, m))
  480.     {
  481.         SetReqGadgets(req, gl);
  482.         if (DoRequest(req, g, std_ghandler))
  483.         {
  484.             g->a.x.ax = str2double(ax_x);
  485.             g->a.x.cstep = str2double(step_x);
  486.             g->a.x.every = str2int(every_x);
  487.             g->a.y.ax = str2double(ax_y);
  488.             g->a.y.cstep = str2double(step_y);
  489.             g->a.y.every = str2int(every_y);
  490.             check_graph(g);
  491.         }
  492.     }
  493.     Free(m);
  494. }
  495.  
  496. /* Change mode */
  497. void set_mode(struct graph *g, int newmode)
  498. {
  499.     if (newmode != g->s.select_mode) deselect(g);
  500.     g->s.select_mode = newmode;
  501. }
  502.  
  503. /* Recalc & display title */
  504. /* assumes GNAMELEN < TITLELEN */
  505. void set_title(struct graph *g)
  506. {
  507.     strcpy(g->io.title, g->name);
  508.     if (!g->ok) strncat(g->io.title, "(Bad)", TITLELEN - 1 - strlen(g->io.title
  509. ));
  510.     if (g->s.current)
  511.         if (g->s.current->name[0] != '\0')
  512.         {
  513.             strncat(g->io.title, ", function is ", TITLELEN - 1 - strlen(g->io.
  514. title));
  515.             strncat(g->io.title, g->s.current->name, TITLELEN - 1 - strlen(g->i
  516. o.title));
  517.             if (!g->s.current->ok) strncat(g->io.title, "(Bad)", TITLELEN - 1 -
  518.  strlen(g->io.title));
  519.         }
  520.         else strncat(g->io.title, ", object selected", TITLELEN -1 - strlen(g->
  521. io.title));
  522.     else strncat(g->io.title, ", nothing selected", TITLELEN - 1 - strlen(g->io
  523. .title));
  524.     if (g->s.mouse)
  525.     {
  526.         char x[NBLEN], y[NBLEN];
  527.  
  528.         double2str(x, g->s.x); double2str(y, g->s.y);
  529.         strncat(g->io.title, "  x=", TITLELEN - 1 - strlen(g->io.title));
  530.         strncat(g->io.title, x, TITLELEN - 1 - strlen(g->io.title));
  531.         strncat(g->io.title, ", y=", TITLELEN - 1 - strlen(g->io.title));
  532.         strncat(g->io.title, y, TITLELEN - 1 - strlen(g->io.title));
  533.     }
  534.  
  535.     SetWindowTitles(g->io.win, g->io.title, "Graph");
  536. }
  537.  
  538. /* Window has changed size, recreate coord system, taking into account the
  539.    desired ratio */
  540. void set_scale(struct graph *g)
  541. {
  542.     /* Standard borders */
  543.     long x0offset = g->io.win->BorderLeft + 8;
  544.     long x1offset = g->io.win->BorderRight + 8;
  545.     long y0offset = g->io.win->BorderBottom + 8;
  546.     long y1offset = g->io.win->BorderTop + 8;
  547.  
  548.     /* Save size used */
  549.     g->io.oldwidth = g->io.win->Width; g->io.oldheight = g->io.win->Height;
  550.  
  551.     /* Delete old coords */
  552.     if (g->io.rw) g->io.rw->delete(g->io.rw);
  553.  
  554.     /* Create new coords at max size */
  555.     g->io.rw = new_RWindow(g->io.win->RPort, g->io.win->Width, g->io.win->Heigh
  556. t,
  557.                           x0offset, y0offset, x1offset, y1offset,
  558.                           g->a.x.min, g->a.y.min, g->a.x.max, g->a.y.max,
  559.                           g->a.x.log, g->a.y.log, TRUE);
  560.     if (!g->io.rw)
  561.     {
  562.         message(g, "Couldn't make coords", (char *)NULL);
  563.         return;
  564.     }
  565.  
  566.     SetRast(g->io.win->RPort, 0); /* Clear whole window */
  567.  
  568.     if (g->ok && g->a.ok && g->a.ratio != NOVAL)
  569.     {
  570.         /* Adjust for desired ratio */
  571.         double r;
  572.  
  573.         /* Current ratio */
  574.         r = g->a.ratio /
  575.             fabs(((g->io.rw->sy(g->io.rw, g->a.y.log ? 10.0 : 2.0) - g->io.rw->
  576. sy(g->io.rw, 1.0)) * scr_dpmx) /
  577.                  ((g->io.rw->sx(g->io.rw, g->a.x.log ? 10.0 : 2.0) - g->io.rw->
  578. sx(g->io.rw, 1.0)) * scr_dpmy));
  579.  
  580.         g->io.rw->delete(g->io.rw);
  581.  
  582.         /* Adjust borders */
  583.         if (r > 1.0) /* make X smaller */
  584.         {
  585.             long width = g->io.win->Width - x0offset - x1offset;
  586.             long delta = width - width / r;
  587.  
  588.             x0offset += delta / 2;
  589.             x1offset += delta - delta / 2;
  590.         }
  591.         else /* make Y smaller */
  592.         {
  593.             long height = g->io.win->Height - y0offset - y1offset;
  594.             long delta = height - height * r;
  595.  
  596.             y0offset += delta / 2;
  597.             y1offset += delta - delta / 2;
  598.         }
  599.         /* & create new coord system */
  600.         g->io.rw = new_RWindow(g->io.win->RPort, g->io.win->Width, g->io.win->H
  601. eight,
  602.                                x0offset, y0offset, x1offset, y1offset,
  603.                                g->a.x.min, g->a.y.min, g->a.x.max, g->a.y.max,
  604.                                g->a.x.log, g->a.y.log, TRUE);
  605.         if (!g->io.rw)
  606.         {
  607.             message(g, "Couldn't make coords", (char *)NULL);
  608.             return;
  609.         }
  610.     }
  611. }
  612.  
  613. /* Change output resolution.
  614.    This may require opening of new resources (eg fonts) in the objects, which
  615.    can obviously fail, yet we don't want the graph to be left in an inconsis-
  616.    tent state, hence inform & confirm (cf object.guidelines). */
  617. int set_dpm(struct graph *g, int dpmx, int dpmy)
  618. {
  619.     int olddpmx = g->io.dpmx, olddpmy = g->io.dpmy;
  620.     struct TextFont *digits;
  621.  
  622.     g->io.dpmx = dpmx; g->io.dpmy = dpmy;
  623.     /* Open correct font for this resolution */
  624.     if (digits = open_font(g, "digits.font", DIGHEIGHT, 0, 0))
  625.     {
  626.         int ok = TRUE;
  627.         struct object *scan, *scan2;
  628.  
  629.         /* Inform objects */
  630.         for (scan = first(&g->o_list); ok && succ(scan); scan = succ(scan))
  631.             if (!scan->inform(scan)) ok = FALSE;
  632.  
  633.         if (ok)
  634.         {
  635.             /* Everything worked, confoirm changes */
  636.             for (scan = first(&g->o_list); ok && succ(scan); scan = succ(scan))
  637.      
  638.                 scan->confirm(scan, TRUE);
  639.  
  640.             /* Free old resources */
  641.             CloseFont(g->io.digits);
  642.             g->io.digits = digits;
  643.             return TRUE;
  644.         }
  645.         /* Return to previous state */
  646.         for (scan2 = first(&g->o_list); scan2 != scan; scan2 = succ(scan2))
  647.             scan2->confirm(scan2, FALSE);
  648.  
  649.         CloseFont(digits);
  650.     }
  651.     else
  652.         message(g, "Couldn't open digits.font", (char *)NULL);
  653.     g->io.dpmx = olddpmx; g->io.dpmy = olddpmy;
  654.     return FALSE;
  655. }
  656.  
  657.     /* Set pen to one ptsize wide. Should really modify g->io.rw->rp, but ... *
  658. /
  659. void set_pensize(struct graph *g, struct RastPort *rp, double ptsize)
  660. {
  661.     int xsize = xinch2dots(g, ptsize / 72.0);
  662.     int ysize = yinch2dots(g, ptsize / 72.0);
  663.  
  664.     if ((xsize & 1) == 0) xsize++;
  665.     if ((ysize & 1) == 0) ysize++;
  666.  
  667.     rp->PenWidth = xsize;
  668.     rp->PenHeight = ysize;
  669. }
  670.  
  671. /* Use a "thin" line (1 pixel wide) */
  672. void set_thin(struct graph *g, struct RastPort *rp)
  673. {
  674.     rp->PenWidth = 1;
  675.     rp->PenWidth = 1;
  676. }
  677.  
  678. /* Change graph limits */
  679. void zoom_in(struct graph *g, double x0, double y0, double x1, double y1)
  680. {
  681.     if (g->ok && x0 != x1 && y0 != y1)
  682.     {
  683.         double xmin = min(x0, x1);
  684.         double xmax = max(x0, x1);
  685.         double ymin = min(y0, y1);
  686.         double ymax = max(y0, y1);
  687.  
  688.         g->a.x.min = max(g->a.x.min, xmin);
  689.         g->a.x.max = min(g->a.x.max, xmax);
  690.         g->a.y.min = max(g->a.y.min, ymin);
  691.         g->a.y.max = min(g->a.y.max, ymax);
  692.  
  693.         check_graph(g);
  694.     }
  695.     else
  696.         message(g, "No rectangle to zoom into", (char *)NULL);
  697. }
  698.  
  699. /* Calculate new limits for ax a for a zoom factor of mult */
  700. static void zoom_ax(struct ax *a, double mult)
  701. {
  702.     if (a->log)
  703.     {
  704.         /* Zoom on a log scale ... Real fun with this one :-) */
  705.         double centre = sqrt(a->min * a->max);
  706.         double delta = pow(centre, 1.0 - mult);
  707.  
  708.         a->min = pow(a->min, mult) * delta;
  709.         a->max = pow(a->max, mult) * delta;
  710.     }
  711.     else
  712.     {
  713.         /* ax becomes mult times longer, with same centre */
  714.         double centre = (a->min + a->max) / 2;
  715.         double delta = centre - a->min;
  716.  
  717.         a->min = centre - mult * delta;
  718.         a->max = centre + mult * delta;
  719.     }
  720. }
  721.  
  722. /* Zoom out by a factor factor */
  723. void zoom_factor(struct graph *g, double factor)
  724. {
  725.     if (g->ok)
  726.     {
  727.         zoom_ax(&g->a.x, factor);
  728.         zoom_ax(&g->a.y, factor);
  729.         check_graph(g);
  730.     }
  731.     else
  732.         message(g, "No scale set !", (char *)NULL);
  733. }
  734.  
  735. /* Define new centre for ax */
  736. static void center_ax(struct ax *a, double centre)
  737. {
  738.     if (a->log)
  739.     {
  740.         /* more & more fun ... */
  741.         double delta = sqrt(a->max / a->min);
  742.  
  743.         a->max = centre * delta;
  744.         a->min = centre / delta;
  745.     }
  746.     else
  747.     {
  748.         /* obvious */
  749.         double delta = (a->max - a->min) / 2.0;
  750.  
  751.         a->max = centre + delta;
  752.         a->min = centre - delta;
  753.     }
  754. }
  755.  
  756. /* New centre for graph */
  757. void center_graph(struct graph *g, double x, double y)
  758. {
  759.     if (g->ok && x != NOVAL)
  760.     {
  761.         center_ax(&g->a.x, max(min(x, g->a.x.max), g->a.x.min));
  762.         center_ax(&g->a.y, max(min(y, g->a.y.max), g->a.y.min));
  763.         check_graph(g);
  764.     }
  765.     else
  766.         message(g, "Nothing to center on", (char *)NULL);
  767. }
  768.  
  769. /* Redraw ax a (of graph g), at position xorig on other ax. y_ax is true for
  770.    drawing of y axis */
  771. static void draw_ax(struct graph *g, struct ax *a, double xorig, int y_ax)
  772. {
  773.     struct TextFont *oldfont, *digits = g->io.digits;
  774.     struct RWindow *const rwin = g->io.rw;
  775.     struct RastPort *const rp = rwin->rp;
  776.     int xtick = yinch2dots(g, XTICK), ytick = xinch2dots(g, YTICK);
  777.     int xtext = xinch2dots(g, XTEXT), ytext = yinch2dots(g, YTEXT);
  778.  
  779.     oldfont = rp->Font;
  780.     SetFont(rp, digits);
  781.     SetDrMd(rp, JAM1);
  782.     if (a->ax != NOVAL)
  783.     {
  784.         /* Draw ax */
  785.         if (y_ax)
  786.         {
  787.             RMove(rwin, a->ax, a->max);
  788.             Draw(rwin->rp, rwin->rp->cp_x, ftol(rwin->sy(rwin, a->min)));
  789.         }
  790.         else
  791.         {
  792.             RMove(rwin, a->max, a->ax);
  793.             Draw(rwin->rp, ftol(rwin->sx(rwin, a->min)), rwin->rp->cp_y);
  794.         }
  795.         if (a->cstep != NOVAL)
  796.         {
  797.             /* Draw ticks and numbers */
  798.             if (a->log)
  799.             {
  800.                 /* logarithmic ax */
  801.                 double nax;
  802.                 int emin, emax, e, count, i;
  803.  
  804.                 /* Normalise origin (0 < nax < 10). There will be count ticks,
  805.                    at nax, nax + cstep, nax + 2 * cstep, etc for every exponent
  806.      
  807.                    value between min & max */
  808.                 nax = xorig * pow(10.0, -floor(log10(xorig)));
  809.                 /* Exponent range */
  810.                 emin = floor(log10(a->min / nax));
  811.                 emax = ceil(log10(a->max / nax));
  812.                 /* Number of ticks for every exponent value */
  813.                 count = ceil(9 * nax / a->cstep);
  814.  
  815.                 for (e = emin; e <= emax; e++)
  816.                 {
  817.                     double const p = pow(10.0, (double)e);
  818.                     double const st = a->cstep * p; /* step between ticks */
  819.                     double x = p * nax; /* POos. of main tick */
  820.                     long cx, cy;
  821.  
  822.                     /* Display main value (at nax, with exponent) */
  823.                     if (a->every != INOVAL && x != xorig)
  824.                     {
  825.                         if (y_ax)
  826.                             RMove(rwin, a->ax, x);
  827.                         else
  828.                             RMove(rwin, x, a->ax);
  829.  
  830.                         cx = rp->cp_x; cy = rp->cp_y;
  831.  
  832.                         if (e == 0)
  833.                         {
  834.                             /*  don't display 10^0 */
  835.                             char nb[NBLEN];
  836.                             int l;
  837.  
  838.                             /* Display nax */
  839.                             double2str(nb, nax);
  840.                             l = strlen(nb);
  841.                             if (y_ax)
  842.                                 Move(rp, cx + xtext, cy + digits->tf_Baseline /
  843.  2);
  844.                             else
  845.                                 Move(rp, cx - digits->tf_XSize * l / 2, cy + di
  846. gits->tf_Baseline + ytext);
  847.                             Text(rp, nb, l);
  848.                         }
  849.                         else
  850.                         {
  851.                             char nb[NBLEN + 3], expo[NBLEN];
  852.                             int l1, l2;
  853.  
  854.                             if (nax == 1)
  855.                                 strcpy(nb, "10");
  856.                             else
  857.                             {
  858.                                 double2str(nb, nax);
  859.                                 strcat(nb, "*10");
  860.                             }
  861.                             sprintf(expo, "%d", e);
  862.                             l1 = strlen(nb);
  863.                             l2 = strlen(expo);
  864.                             /* Display base */
  865.                             if (y_ax)
  866.                                 Move(rp, cx + xtext, cy + digits->tf_Baseline /
  867.  2);
  868.                             else
  869.                                 Move(rp, cx, cy + digits->tf_Baseline + digits-
  870. >tf_Baseline / 2 + ytext);
  871.                             Text(rp, nb, l1);
  872.                             /* Display exponent */
  873.                             Move(rp, rp->cp_x, rp->cp_y - digits->tf_Baseline /
  874.  2);
  875.                             Text(rp, expo, l2);
  876.  
  877.                         }
  878.                     }
  879.                     /* Now for the ticks ... */
  880.                     for (i = 0; i < count; i++, x += st)
  881.                     {
  882.                         /* Draw tick */
  883.                         if (y_ax)
  884.                             RMove(rwin, a->ax, x);
  885.                         else
  886.                             RMove(rwin, x, a->ax);
  887.  
  888.                         cx = rp->cp_x; cy = rp->cp_y;
  889.  
  890.                         if (y_ax)
  891.                         {
  892.                             Move(rp, cx + ytick, cy);
  893.                             Draw(rp, cx - ytick, cy);
  894.                         }
  895.                         else
  896.                         {
  897.                             Move(rp, cx, cy + xtick);
  898.                             Draw(rp, cx, cy - xtick);
  899.                         }
  900.                         /* Display digits */
  901.                         if (i != 0 && a->every != INOVAL && (i % a->every) == 0
  902. )
  903.                         {
  904.                             char nb[NBLEN];
  905.                             int l;
  906.  
  907.                             /* Only display mantissa */
  908.                             double2str(nb, nax + i * a->cstep);
  909.                             l = strlen(nb);
  910.                             if (y_ax)
  911.                                 Move(rp, cx + xtext, cy + digits->tf_Baseline /
  912.  2);
  913.                             else
  914.                                 Move(rp, cx - digits->tf_XSize * l / 2 + 1, cy
  915. + digits->tf_Baseline + ytext);
  916.                             Text(rp, nb, l);
  917.                         }
  918.                     }
  919.                 }
  920.             }
  921.             else
  922.             {
  923.                 /* linear ax */
  924.                 long count, disp_digits;
  925.                 double x;
  926.  
  927.                 /* Number of ticks */
  928.                 count = ceil((a->min - xorig) / a->cstep);
  929.                 /* First tick at which to show value */
  930.                 disp_digits = a->every == INOVAL ?
  931.                                 count - 1 : /* No digits displayed */
  932.                                 a->every * (long)ceil((a->min - xorig) / (a->cs
  933. tep * a->every));
  934.  
  935.                 for(x = count * a->cstep + xorig; x <= a->max; x += a->cstep, c
  936. ount++)
  937.                 {
  938.                     long cx, cy;
  939.  
  940.                     /* Draw tick */
  941.                     if (y_ax)
  942.                         RMove(rwin, a->ax, x);
  943.                     else
  944.                         RMove(rwin, x, a->ax);
  945.  
  946.                     cx = rp->cp_x; cy = rp->cp_y;
  947.  
  948.                     if (y_ax)
  949.                     {
  950.                         Move(rp, cx + ytick, cy);
  951.                         Draw(rp, cx - ytick, cy);
  952.                     }
  953.                     else
  954.                     {
  955.                         Move(rp, cx, cy + xtick);
  956.                         Draw(rp, cx, cy - xtick);
  957.                     }
  958.  
  959.                     /* Display digits */
  960.                     if (count == disp_digits)
  961.                     {
  962.                         char nb[NBLEN];
  963.                         int l;
  964.  
  965.                         /* Next one in every ticks */
  966.                         disp_digits += a->every;
  967.  
  968.                         if (count != 0) /* Not at origin */
  969.                         {
  970.                             double2str(nb, x);
  971.                             l = strlen(nb);
  972.                             if (y_ax)
  973.                                 Move(rp, cx + xtext, cy + digits->tf_Baseline /
  974.  2);
  975.                             else
  976.                                 Move(rp, cx - digits->tf_XSize * l / 2, cy + di
  977. gits->tf_Baseline + ytext);
  978.                             Text(rp, nb, l);
  979.                         }
  980.                     }
  981.                 }
  982.             }
  983.         }
  984.     }
  985.     SetFont(rp, oldfont);
  986. }
  987.  
  988. /* Draws directly into the rastport, used internally. allow_mes nust be FALSE
  989.    if called during window refresh */
  990. static void do_draw(struct graph *g, int allow_mes)
  991. {
  992.     struct object *o;
  993.  
  994.     if (g->ok && g->io.rw)
  995.     {
  996.         /* Draw axes */
  997.         SetAPen(g->io.rw->rp, 1L);
  998.         if (g->a.ok)
  999.         {
  1000.             draw_ax(g, &g->a.x, g->a.y.ax, FALSE);
  1001.             draw_ax(g, &g->a.y, g->a.x.ax, TRUE);
  1002.         }
  1003.  
  1004.         /* Draw objects */
  1005.         for (o = first(&g->o_list); succ(o); o = succ(o))
  1006.             if (o != g->s.current && o->ok) o->draw(o, allow_mes);
  1007.  
  1008.         /* Current object is always last so that it appears "on top" */
  1009.         if (g->s.current && g->s.current->ok) g->s.current->draw(g->s.current,
  1010. allow_mes);
  1011.     }
  1012. }
  1013.  
  1014. /* Redraw graph completely */
  1015. void draw_graph(struct graph *g, int allow_mes)
  1016. {
  1017.     if (allow_mes) set_title(g);
  1018.  
  1019.     SetRast(g->io.rw->rp, 0); /* Clear window */
  1020.  
  1021.     do_draw(g, allow_mes);
  1022. }
  1023.  
  1024. /* Redraw graph partially (ref is NULL for no redraw). ref is disposed when
  1025.    refresh is done */
  1026. void refresh_graph(struct graph *g, int allow_mes, struct Region *ref)
  1027. {
  1028.     if (ref)
  1029.     {
  1030.         if (g->io.rw)
  1031.         {
  1032.             /* Setup clipping */
  1033.             struct Region *oldRegion = InstallClipRegion(g->io.rw->rp->Layer, r
  1034. ef);
  1035.  
  1036.             SetRast(g->io.rw->rp, 0);
  1037.             do_draw(g, allow_mes);
  1038.  
  1039.             InstallClipRegion(g->io.rw->rp->Layer, oldRegion);
  1040.         }
  1041.         DisposeRegion(ref);
  1042.     }
  1043. }
  1044.  
  1045. /* Returns a region that will fully refresh g.
  1046.    (makes a copy of the current region) */
  1047. struct Region *full_refresh(struct graph *g)
  1048. {
  1049.     struct Region *r;
  1050.  
  1051.     if ((r = NewRegion()) && g->io.rw)
  1052.     {
  1053.         struct Region *old = InstallClipRegion(g->io.rw->rp->Layer, NULL);
  1054.  
  1055.         /* Make copy */
  1056.         if (!OrRegionRegion(old, r))
  1057.         {
  1058.             DisposeRegion(r);
  1059.             r = NULL;
  1060.         }
  1061.  
  1062.         InstallClipRegion(g->io.rw->rp->Layer, old);
  1063.     }
  1064.     return r;
  1065. }
  1066.  
  1067. /* Open printer.device */
  1068. static int open_prt(void)
  1069. {
  1070.     if (prtport = CreatePort(0L, 0L))
  1071.     {
  1072.         if (prtio = (struct IODRPReq *)CreateExtIO(prtport, sizeof(struct IODRP
  1073. Req)))
  1074.         {
  1075.             if (OpenDevice("printer.device", 0, (struct IORequest *)prtio, 0) =
  1076. = 0)
  1077.             {
  1078.                 ped = &((struct PrinterData *)prtio->io_Device)->pd_SegmentData
  1079. ->ps_PED;
  1080.                 prtprefs = &((struct PrinterData *)prtio->io_Device)->pd_Prefer
  1081. ences;
  1082.  
  1083.                 return TRUE;
  1084.             }
  1085.             DeleteExtIO((struct IORequest *)prtport);
  1086.         }
  1087.         DeletePort(prtport);
  1088.     }
  1089.     return FALSE;
  1090. }
  1091.  
  1092. /* Close printer device */
  1093. static void close_prt(void)
  1094. {
  1095.     CloseDevice((struct IORequest *)prtio);
  1096.     DeleteExtIO((struct IORequest *)prtio);
  1097.     DeletePort(prtport);
  1098. }
  1099.  
  1100. /* Easy access to DumpRPort. wait : DoIO or SendIO ? */
  1101. static void prt_raster(int wait, struct RastPort *rp, struct ColorMap *cm, ULON
  1102. G m, UWORD sx, UWORD sy, UWORD w, UWORD h, LONG dc, LONG dr, UWORD special)
  1103. {
  1104.     prtio->io_RastPort = rp;
  1105.     prtio->io_ColorMap = cm;
  1106.     prtio->io_Modes = m;
  1107.     prtio->io_SrcX = sx;
  1108.     prtio->io_SrcY = sy;
  1109.     prtio->io_SrcWidth = w;
  1110.     prtio->io_SrcHeight = h;
  1111.     prtio->io_DestCols = dc;
  1112.     prtio->io_DestRows = dr;
  1113.     prtio->io_Special = special;
  1114.     prtio->io_Command = PRD_DUMPRPORT;
  1115.     if (wait) DoIO(prtio);
  1116.     else SendIO(prtio);
  1117. }
  1118.  
  1119. /* Print a slice of the dump to the printer */
  1120. static int prt_slice(struct graph *g, struct RastPort *rp, int w, int h, int y,
  1121.  int slice, struct Requester *abreq)
  1122. {
  1123.     struct RWindow *old = g->io.rw;
  1124.     int ret = FALSE;
  1125.     ULONG prtsig = 1 << prtio->io_Message.mn_ReplyPort->mp_SigBit;
  1126.     ULONG winsig = 1 << g->io.win->UserPort->mp_SigBit;
  1127.  
  1128.     /* Create coords for slice */
  1129.     g->io.rw = new_RWindow(rp, w, slice,
  1130.                            0, y + slice - h, 0, -y,
  1131.                            g->a.x.min, g->a.y.min, g->a.x.max, g->a.y.max,
  1132.                            g->a.x.log, g->a.y.log, FALSE);
  1133.     if (g->io.rw)
  1134.     {
  1135.         int done = FALSE;
  1136.  
  1137.         /* Draw into rastport. Note that it may be > 1024 x 1024 ! */
  1138.         BigSetRast(rp, 0);
  1139.         do_draw(g, TRUE);
  1140.         prt_raster(FALSE, rp, prtcm, 0L, 0, 0, (UWORD)w, (UWORD)slice, w, slice
  1141. , SPECIAL_NOFORMFEED);
  1142.  
  1143.         do { /* Wait for end of printing or abort */
  1144.             Wait(prtsig | winsig);
  1145.             if (aborted(abreq))
  1146.             {
  1147.                 done = TRUE;
  1148.                 ret = FALSE; /* Stop! */
  1149.                 if (!CheckIO(prtio))
  1150.                 {
  1151.                     AbortIO(prtio);
  1152.                     WaitIO(prtio);
  1153.                 }
  1154.                 prtio->io_Error = 0;
  1155.             }
  1156.             else if (CheckIO(prtio))
  1157.             {
  1158.                 done = TRUE;
  1159.                 ret = prtio->io_Error == 0;
  1160.             }
  1161.         } while (!done);
  1162.         g->io.rw->delete(g->io.rw);
  1163.     }
  1164.     else
  1165.         nomem(g->io.win);
  1166.     g->io.rw = old;
  1167.     return ret;
  1168. }
  1169.  
  1170. /* Work out the max size for a slice (don't use more than half the largest
  1171.    chunk of chip ram). The height must be a multiple of quanta */
  1172. static int get_slice_height(int w, int h, int quanta)
  1173. {
  1174.     long use = AvailMem(MEMF_CHIP | MEMF_LARGEST) / 2;
  1175.     long min = 2 * RASSIZE(w, quanta);
  1176.     long nb = use / min;
  1177.  
  1178.     if (nb == 0) /* Not much mem ... */ return quanta;
  1179.     else if (nb * quanta > h) return h;
  1180.     else return nb * quanta;
  1181. }
  1182.  
  1183. /* Create a 2 plane Rastport with clipping at its limits (-> Layer)  */
  1184. static struct RastPort *alloc_ras(int w, int h)
  1185. {
  1186.     struct Layer_Info *li;
  1187.     struct BitMap *bm;
  1188.     BYTE *data;
  1189.     struct Layer *l;
  1190.  
  1191.     /* Alloc components */
  1192.     if (li = NewLayerInfo())
  1193.     {
  1194.         if (bm = AllocMem(sizeof(struct BitMap), 0L))
  1195.         {
  1196.             if (data = AllocMem(2 * RASSIZE(w, h), MEMF_CHIP))
  1197.             {
  1198.                 /* Set up data structure */
  1199.                 InitBitMap(bm, 2, w, h);
  1200.                 bm->Planes[0] = (PLANEPTR)data;
  1201.                 bm->Planes[1] = (PLANEPTR)(data + RASSIZE(w, h));
  1202.  
  1203.                 if (l = CreateUpfrontLayer(li, bm, 0, 0, w - 1, h - 1, LAYERSIM
  1204. PLE, NULL))
  1205.                     return l->rp;
  1206.  
  1207.                 FreeMem(data, 2 * RASSIZE(w, h));
  1208.             }
  1209.             FreeMem(bm, sizeof(struct BitMap));
  1210.         }
  1211.         DisposeLayerInfo(li);
  1212.     }
  1213.     return NULL;
  1214. }
  1215.  
  1216. /* Free rastport created by alloc_ras */
  1217. static void free_ras(struct RastPort *rp)
  1218. {
  1219.     struct Layer_Info *li = rp->Layer->LayerInfo;
  1220.     struct BitMap *bm = rp->BitMap;
  1221.  
  1222.     DeleteLayer(li, rp->Layer);
  1223.     FreeMem(bm->Planes[0], bm->BytesPerRow * bm->Rows * bm->Depth);
  1224.     FreeMem(bm, sizeof(struct BitMap));
  1225.     DisposeLayerInfo(li);
  1226. }
  1227.  
  1228. /* Print graph into a bitmap which is w by h pixels, resolution xdpm, ydpm,
  1229.    quanta is the size of the print head (if any, 1 otherwise). dump_slice is
  1230.    called for every slice.
  1231.    Rem: printing is broken into horizontal slices, according to available
  1232.    memory */
  1233. static void prt(struct graph *g, int w, int h, int quanta, int xdpm, int ydpm,
  1234. prtfunc dump_slice)
  1235. {
  1236.     int slice, y, ok = TRUE;
  1237.     struct RastPort *rp;
  1238.     struct Requester *abreq;
  1239.     struct Requester *req;
  1240.     struct Memory *m;
  1241.     struct Gadget *gl = NULL, *thin;
  1242.     char size[NBLEN];
  1243.  
  1244.     size[0] = '\0';
  1245.  
  1246.     /* Ask user for print characteristics (pen size) */
  1247.     if ((m = NewMemory()) &&
  1248.         (req = InitReq(50, 20, 180, 85, m)) &&
  1249.         SetReqBorder(req, 1, m) &&
  1250.         AddIntuiText(&req->ReqText, "Print Characteristics", 6, 6, m) &&
  1251.         (thin = AddRadio(&gl, 0, "Thin", TRUE, SELECTED, RELVERIFY, 2, 11, 20,
  1252. 10, 10, m)) &&
  1253.         AddRadio(&gl, 0, "Thick,", TRUE, 0, RELVERIFY, 1, 11, 40, 10, 10, m) &&
  1254.      
  1255.         AddText(&gl, 0, "Size ", FALSE, size, NBLEN, TRUE, 0, RELVERIFY, 128, 4
  1256. 1, 32, 10, TRUE, m) &&
  1257.         AddBox(&gl, TRUE, "Ok", 0, RELVERIFY | ENDGADGET, 13, 60, 65, 15, FALSE
  1258. , m) &&
  1259.         AddBox(&gl, FALSE, "Cancel", 0, RELVERIFY | ENDGADGET, 103, 60, 65, 15,
  1260.  FALSE, m))
  1261.     {
  1262.         SetReqGadgets(req, gl);
  1263.         if (DoRequest(req, g, std_ghandler))
  1264.         {
  1265.             double ptsize = str2double(size);
  1266.  
  1267.             if ((thin->Flags & SELECTED) || (ptsize > 0.0 && ptsize != NOVAL))
  1268.             {
  1269.                 /* Set up for printing */
  1270.                 if (set_dpm(g, xdpm, ydpm))
  1271.                 {
  1272.                     slice = get_slice_height(w, h, quanta);
  1273.                     if (rp = alloc_ras(w, slice))
  1274.                     {
  1275.                         if (abreq = abort_request(g, "Printing, slice
  1276.  "))
  1277.                         {
  1278.                             if (thin->Flags & SELECTED) set_thin(g, rp);
  1279.                             else set_pensize(g, rp, ptsize);
  1280.  
  1281.                             /* Print all the slices */
  1282.                             for (y = 0; ok && y < h; y += slice)
  1283.                             {
  1284.                                 char msg[30];
  1285.  
  1286.                                 sprintf(msg, "Printing, slice %d of %d", y / sl
  1287. ice + 1, (h + slice - 1) / slice);
  1288.                                 set_abort_msg(abreq, msg);
  1289.                                 ok = dump_slice(g, rp, w, h, y, min(slice, h -
  1290. y), abreq);
  1291.                             }
  1292.                             end_abort_request(abreq);
  1293.                         }
  1294.                         else
  1295.                             message(g, "Abort requester failed", (char *)NULL);
  1296.      
  1297.                         free_ras(rp);
  1298.                     }
  1299.                     else
  1300.                         nomem(g->io.win);
  1301.                     /* Return to screen */
  1302.                     set_dpm(g, scr_dpmx, scr_dpmy);
  1303.                 }
  1304.             }
  1305.             else
  1306.                 message(g, "Illegal line size", (char *)NULL);
  1307.         }
  1308.     }
  1309.     Free(m);
  1310. }
  1311.  
  1312. /* get max size of printed output */
  1313. static int get_max_size(struct graph *g, int *w, int *h, int *quanta)
  1314. {
  1315.     prt_raster(TRUE, g->io.win->RPort, prtcm, 0L, 0, 0, g->io.win->Width, g->io
  1316. .win->Height, LONG_MAX, LONG_MAX, SPECIAL_NOPRINT);
  1317.  
  1318.     *w = prtio->io_DestCols;
  1319.     *h = prtio->io_DestRows;
  1320.     /* If length = infinity, use MAXPRINTPAGES pages */
  1321.     if (*h == LONG_MAX)
  1322.         *h = (ped->ped_YDotsInch * MAXPRINTPAGES * prtprefs->PaperLength) / (pr
  1323. tprefs->PrintSpacing == SIX_LPI ? 6 : 8);
  1324.     *quanta = ped->ped_NumRows;
  1325.  
  1326.     return prtio->io_Error == 0;
  1327. }
  1328.  
  1329. /* Determine size which fits asked for ratio (when no absolute size set in
  1330.    preferences) */
  1331. static int get_size(struct graph *g, int *w, int *h, int *quanta)
  1332. {
  1333.     if (get_max_size(g, w, h, quanta))
  1334.     {
  1335.         if (g->ok && g->a.ok && g->a.ratio != NOVAL)
  1336.         {
  1337.             /* adjust for ratio */
  1338.             double r = g->a.ratio /
  1339.                 fabs((*h / (g->a.y.log ? log10(g->a.y.max / g->a.y.min) : (g->a
  1340. .y.max - g->a.y.min)) * ped->ped_XDotsInch) /
  1341.                      (*w / (g->a.x.log ? log10(g->a.x.max / g->a.x.min) : (g->a
  1342. .x.max - g->a.x.min)) * ped->ped_YDotsInch));
  1343.  
  1344.             if (r > 1.0) /* make X smaller */
  1345.                 *w /= r;
  1346.             else /* make Y smaller */
  1347.                 *h *= r;
  1348.         }
  1349.         if (*w == 0) *w = 1;
  1350.         if (*h == 0) *h = 1;
  1351.         return TRUE;
  1352.     }
  1353.     return FALSE;
  1354. }
  1355.  
  1356. /* determine size to use when user has set abs. size in preferences */
  1357. static int get_abs_size(struct graph *g, int *w, int *h, int *quanta)
  1358. {
  1359.     if (get_max_size(g, w, h, quanta))
  1360.     {
  1361.         if (g->ok && g->a.ok && g->a.ratio != NOVAL)
  1362.         {
  1363.             /* Adjust only if dimension is free (size set to 0) */
  1364.             double r = g->a.ratio /
  1365.                 fabs((*h / (g->a.y.log ? log10(g->a.y.max / g->a.y.min) : (g->a
  1366. .y.max - g->a.y.min)) * ped->ped_XDotsInch) /
  1367.                      (*w / (g->a.x.log ? log10(g->a.x.max / g->a.x.min) : (g->a
  1368. .x.max - g->a.x.min)) * ped->ped_YDotsInch));
  1369.  
  1370.             if (r > 1.0 && prtprefs->PrintMaxWidth == 0) /* make X smaller */
  1371.                 *w /= r;
  1372.             else if (r < 1.0 && prtprefs->PrintMaxHeight == 0) /* make Y smalle
  1373. r */
  1374.                 *h *= r;
  1375.         }
  1376.         if (*w == 0) *w = 1;
  1377.         if (*h == 0) *h = 1;
  1378.         return TRUE;
  1379.     }
  1380.     return FALSE;
  1381. }
  1382.  
  1383. /* Print a graph */
  1384. void prt_graph(struct graph *g)
  1385. {
  1386.     int w, h, quanta;
  1387.  
  1388.     if (g->ok)
  1389.         if (open_prt())
  1390.         {
  1391.             prtprefs->PrintFlags &= ~INTEGER_SCALING; /* We produce nice output
  1392.  anyway ! */
  1393.             prtprefs->PrintAspect = ASPECT_HORIZ; /* No support for vertical pl
  1394. ots */
  1395.  
  1396.             switch (prtprefs->PrintFlags & DIMENSIONS_MASK)
  1397.             {
  1398.                 case MULTIPLY_DIMENSIONS: /* Ignored */
  1399.                     prtprefs->PrintFlags &= ~DIMENSIONS_MASK;
  1400.                     /* FALLTHROUGH */
  1401.                 case IGNORE_DIMENSIONS:
  1402.                 case BOUNDED_DIMENSIONS:
  1403.                     if (get_size(g, &w, &h, &quanta))
  1404.                     {
  1405.                         prtprefs->PrintFlags &= ~DIMENSIONS_MASK;
  1406.                         prt(g, w, h, quanta, (int)(ped->ped_XDotsInch / INCH),
  1407. (int)(ped->ped_YDotsInch / INCH), prt_slice);
  1408.                     }
  1409.                     break;
  1410.  
  1411.                 case ABSOLUTE_DIMENSIONS:
  1412.                 case PIXEL_DIMENSIONS:
  1413.                     if (get_abs_size(g, &w, &h, &quanta))
  1414.                     {
  1415.                         prtprefs->PrintFlags &= ~DIMENSIONS_MASK;
  1416.                         prt(g, w, h, quanta, (int)(ped->ped_XDotsInch / INCH),
  1417. (int)(ped->ped_YDotsInch / INCH), prt_slice);
  1418.                     }
  1419.                     break;
  1420.             }
  1421.             if (prtio->io_Error > 0) /* Printer error */
  1422.                 message(g, "Printer Error", prt_error[prtio->io_Error > MAX_PRT
  1423. _ERROR ? 0 : prtio->io_Error], (char *)NULL);
  1424.             close_prt();
  1425.         }
  1426.         else
  1427.             message(g, "Couldn't open printer\n", (char *)NULL);
  1428. }
  1429.  
  1430. /* Write slice into body. Must add byteRun1 compression */
  1431. static int write_iffslice(struct BitMap *bm)
  1432. {
  1433.     int y, plane;
  1434.  
  1435.     for (y = 0; y < bm->Rows; y++)
  1436.     {
  1437.         /* Interleave bit planes */
  1438.         for (plane = 0; plane < bm->Depth; plane++)
  1439.             if (!fwrite(bm->Planes[plane] + y * bm->BytesPerRow, bm->BytesPerRo
  1440. w, 1, iff_file))
  1441.                 return FALSE;
  1442.     }
  1443.     return TRUE;
  1444. }
  1445.  
  1446. /* Draw & write into slice for iff output */
  1447. static int iff_slice(struct graph *g, struct RastPort *rp, int w, int h, int y,
  1448.  int slice, struct Requester *abreq)
  1449. {
  1450.     struct RWindow *old = g->io.rw;
  1451.     int ret = FALSE;
  1452.  
  1453.     g->io.rw = new_RWindow(rp, w, slice,
  1454.                            0, y + slice - h, 0, -y,
  1455.                            g->a.x.min, g->a.y.min, g->a.x.max, g->a.y.max,
  1456.                            g->a.x.log, g->a.y.log, FALSE);
  1457.     if (g->io.rw)
  1458.     {
  1459.         BigSetRast(rp, 0);
  1460.         do_draw(g, TRUE);
  1461.         if (write_iffslice(rp->BitMap))
  1462.         {
  1463.             if (!aborted(abreq)) ret = TRUE;
  1464.         }
  1465.         else
  1466.             message(g, "Error writing file", (char *)NULL);
  1467.  
  1468.         g->io.rw->delete(g->io.rw);
  1469.     }
  1470.     else
  1471.         nomem(g->io.win);
  1472.     g->io.rw = old;
  1473.     return ret;
  1474. }
  1475.  
  1476. /* Write ILBM header to file (BMHD, CMAP & start of BODY). Save positions for
  1477.    writing sizes */
  1478. static int start_iff(char *name, int w, int h, int xdpi, int ydpi)
  1479. {
  1480.     if (iff_file = fopen(name, "w"))
  1481.     {
  1482.         if (fwrite("FORM", 4, 1, iff_file) && (form_pos = ftell(iff_file)) != -
  1483. 1L &&
  1484.             fwrite("\0\0\0\0ILBMBMHD\0\0\0\024", 16, 1, iff_file))
  1485.         {
  1486.             static BitMapHeader bm = {
  1487.                 0, 0, 0, 0, 2, mskNone, cmpNone, 0, 0
  1488.             };
  1489.             bm.w = w; bm.h = h;
  1490.             bm.xAspect = ydpi; bm.yAspect = xdpi;
  1491.             bm.pageWidth = w; bm.pageHeight = h;
  1492.  
  1493.             if (fwrite((char *)&bm, sizeof(bm), 1, iff_file) &&
  1494.                 fwrite("CMAP\0\0\0\14", 8, 1, iff_file) &&
  1495.                 fwrite("\xff\xff\xff\0\0\0\0\0\0\0\0\0", 12, 1, iff_file) &&
  1496.                 fwrite("BODY", 4, 1, iff_file) && (body_pos = ftell(iff_file))
  1497. != -1 &&
  1498.                 fwrite("\0\0\0\0", 4, 1, iff_file))
  1499.             {
  1500.                 return TRUE;
  1501.             }
  1502.         }
  1503.         /* If failed, delete file */
  1504.         if (fclose(iff_file) == 0) unlink(name);
  1505.     }
  1506.     return FALSE;
  1507. }
  1508.  
  1509. /* Write end of iff_file, ie pad, write lengths & close */
  1510. static int end_iff(void)
  1511. {
  1512.     long end = ftell(iff_file), end2 = end;
  1513.  
  1514.     if (end != -1)
  1515.     {
  1516.         if ((end & 1) == 0 || (end2++, fwrite("\0", 1, 1, iff_file))) /* pad. y
  1517. uck. */
  1518.         {
  1519.             long formlen = end2 - form_pos - 4; /* Includes pad byte */
  1520.             long bodylen = end - body_pos - 4;  /* Doesn't */
  1521.  
  1522.             if (fseek(iff_file, form_pos, 0) != -1 &&
  1523.                 fwrite((char *)&formlen, sizeof(long), 1, iff_file) &&
  1524.                 fseek(iff_file, body_pos, 0) != -1 &&
  1525.                 fwrite((char *)&bodylen, sizeof(long), 1, iff_file))
  1526.             {
  1527.                 return fclose(iff_file) == 0;
  1528.             }
  1529.         }
  1530.     }
  1531.     return FALSE;
  1532. }
  1533.  
  1534. /* Handle iff requester: display file req when user clicks on disk */
  1535. static int iff_handler(struct Gadget *gg, ULONG class, struct Requester *req, s
  1536. truct graph *g)
  1537. {
  1538.     if (gg->GadgetID == IFFDISK)
  1539.     {
  1540.         char file[FILELEN];
  1541.  
  1542.         if (getfile(file, "IFF Output file"))
  1543.         {
  1544.             UWORD pos = RemoveGList(req->RWindow, iffg, 1);
  1545.             strcpy(iff_filename, file);
  1546.             RefreshGList(iffg, req->RWindow, req, 1);
  1547.             AddGList(req->RWindow, iffg, pos, 1, req);
  1548.         }
  1549.         ActivateGadget(iffg, req->RWindow, req);
  1550.         return FALSE;
  1551.     }
  1552.     else return std_ghandler(gg, class, req, g);
  1553. }
  1554.  
  1555. /* Output graph as ILBM. Asks for bitmap size, and resolution. Ignores graph
  1556.    ratio. */
  1557. void iff_todisk(struct graph *g)
  1558. {
  1559.     struct Requester *req;
  1560.     struct Memory *m;
  1561.     struct Gadget *gl = NULL;
  1562.     char xsize[NBLEN], ysize[NBLEN], xdpi[NBLEN], ydpi[NBLEN];
  1563.  
  1564.     xsize[0] = '\0'; ysize[0] = '\0';
  1565.     strcpy(xdpi, "72"); strcpy(ydpi, "72");
  1566.  
  1567.     iff_filename[0] = '\0';
  1568.     if ((m = NewMemory()) &&
  1569.         (req = InitReq(50, 20, 280, 110, m)) &&
  1570.         SetReqBorder(req, 1, m) &&
  1571.         AddIntuiText(&req->ReqText, "Write IFF", 104, 6, m) &&
  1572.         (iffg = AddText(&gl, 0, "File ", FALSE, iff_filename, FILELEN, TRUE, 0,
  1573.  RELVERIFY, 51, 20, 144, 10, TRUE, m)) &&
  1574.         AddBox(&gl, IFFDISK, "Disk", 0, RELVERIFY, 205, 17, 65, 15, FALSE, m) &
  1575. &
  1576.         AddText(&gl, 0, "Size: X ", FALSE, xsize, INTLEN, TRUE, 0, RELVERIFY, 7
  1577. 5, 45, 32, 10, TRUE, m) &&
  1578.         AddText(&gl, 0, "Y ", FALSE, ysize, INTLEN, TRUE, 0, RELVERIFY, 140, 45
  1579. , 32, 10, TRUE, m) &&
  1580.         AddText(&gl, 0, "DPI: X ", FALSE, xdpi, INTLEN, TRUE, 0, RELVERIFY, 83,
  1581.  65, 32, 10, TRUE, m) &&
  1582.         AddText(&gl, 0, "Y ", FALSE, ydpi, INTLEN, TRUE, 0, RELVERIFY, 140, 65,
  1583.  32, 10, TRUE, m) &&
  1584.         AddBox(&gl, TRUE, "Ok", 0, RELVERIFY | ENDGADGET, 38, 85, 65, 15, FALSE
  1585. , m) &&
  1586.         AddBox(&gl, FALSE, "Cancel", 0, RELVERIFY | ENDGADGET, 178, 85, 65, 15,
  1587.  FALSE, m))
  1588.     {
  1589.         SetReqGadgets(req, gl);
  1590.         if (DoRequest(req, g, iff_handler))
  1591.         {
  1592.             int w = str2int(xsize);
  1593.             int h = str2int(ysize);
  1594.             int xdp = str2int(xdpi);
  1595.             int ydp = str2int(ydpi);
  1596.  
  1597.             if (w > 0 && h > 0 && xdp > 0 && ydp > 0 &&
  1598.                 w != NOVAL && h != NOVAL && xdp != NOVAL && ydp != NOVAL)
  1599.                 if (start_iff(iff_filename, w, h, xdp, ydp))
  1600.                 {
  1601.                     prt(g, w, h, 1, (int)(xdp / INCH), (int)(ydp / INCH), iff_s
  1602. lice);
  1603.                     if (!end_iff())
  1604.                         message(g, "Error writing file", (char *)NULL);
  1605.                 }
  1606.                 else
  1607.                     message(g, "Error writing file", (char *)NULL);
  1608.             else
  1609.                 message(g, "Illegal dimensions !", (char *)NULL);
  1610.         }
  1611.     }
  1612.     Free(m);
  1613. }
  1614.  
  1615. /* Write graph to file f */
  1616. int save_graph(struct graph *g, FILE *f)
  1617. {
  1618.     int ok = FALSE;
  1619.     short tag = GRAPH_TAG;
  1620.  
  1621.     /* Write graph information */
  1622.     if (WRITE(f, tag) &&
  1623.         WRITE(f, g->io.win->LeftEdge) &&
  1624.         WRITE(f, g->io.win->TopEdge) &&
  1625.         WRITE(f, g->io.win->Width) &&
  1626.         WRITE(f, g->io.win->Height) &&
  1627.         WRITE(f, g->name) &&
  1628.         WRITE(f, g->a))
  1629.     {
  1630.         struct object *scan;
  1631.  
  1632.         ok = TRUE;
  1633.         /* Write all objects */
  1634.         for (scan = first(&g->o_list); ok && succ(scan); scan = succ(scan))
  1635.         {
  1636.             if (!scan->save(scan, f)) ok = FALSE;
  1637.         }
  1638.         /* Write end tag */
  1639.         if (ok)
  1640.         {
  1641.             tag = GRAPH_END;
  1642.             ok = WRITE(f, tag);
  1643.             g->saved = TRUE;
  1644.         }
  1645.     }
  1646.     return ok;
  1647. }
  1648.  
  1649. /* Load & create new graph from file f (graph tag already read) */
  1650. struct graph *load_graph(struct graph *from, FILE *f)
  1651. {
  1652.     struct graph *g = new_graph(from);
  1653.  
  1654.     if (g)
  1655.     {
  1656.         WORD leftedge, topedge, width, height;
  1657.         int ok = FALSE;
  1658.  
  1659.         /* Read graph info */
  1660.         if (READ(f, leftedge) &&
  1661.             READ(f, topedge) &&
  1662.             READ(f, width) &&
  1663.             READ(f, height) &&
  1664.             READ(f, g->name) &&
  1665.             READ(f, g->a))
  1666.         {
  1667.             int done = FALSE;
  1668.  
  1669.             ok = TRUE;
  1670.             do /* Read objects */
  1671.             {
  1672.                 short tag;
  1673.                 struct object *o = NULL;
  1674.  
  1675.                 if (READ(f, tag))
  1676.                     switch (tag) /* We have to know which objects exist ... */
  1677.                     {
  1678.                         case GRAPH_END:
  1679.                             done = TRUE;
  1680.                             break;
  1681.                         case LABEL_TAG:
  1682.                             o = (struct object *)load_label(g, f);
  1683.                             break;
  1684.                         case FUNCTION_TAG:
  1685.                             o = (struct object *)load_function(g, f);
  1686.                             break;
  1687.                         default:
  1688.                             message(g, "File is not a graph", (char *)NULL);
  1689.                             break;
  1690.                     }
  1691.                 ok = done || o != NULL;
  1692.                 if (o)
  1693.                     add_tail(&g->o_list, o);
  1694.             } while (!done && ok);
  1695.  
  1696.         }
  1697.         if (!ok)
  1698.         {
  1699.             delete_graph(g);
  1700.             g = NULL;
  1701.         }
  1702.         else /* All ok, display */
  1703.         {
  1704.             check_graph(g);
  1705.             g->saved = TRUE;
  1706.         }
  1707.     }
  1708.     return g;
  1709. }
  1710.  
  1711. /* Delete a graph */
  1712. void delete_graph(struct graph *g)
  1713. {
  1714.     struct object *o, *next;
  1715.  
  1716.     /* Deselect */
  1717.     if (g->s.current) g->s.current->deselect(g->s.current);
  1718.  
  1719.     /* Delete all objects */
  1720.     for (o = first(&g->o_list); next = succ(o); o = next)
  1721.     {
  1722.         struct Region *ref = o->delete(o);
  1723.  
  1724.         DisposeRegion(ref);
  1725.     }
  1726.  
  1727.     /* Delete all local resources */
  1728.     if (g->io.rw) g->io.rw->delete(g->io.rw);
  1729.     CloseFont(g->io.digits);
  1730.     cleanup_uio(g); /* Clears menus, closes window ... */
  1731.     FreeMem(g, sizeof(struct graph));
  1732. }
  1733.  
  1734. /* Create a new graph */
  1735. struct graph *new_graph(struct graph *from)
  1736. {
  1737.     struct graph *const g = AllocMem(sizeof(struct graph), 0L);
  1738.     char *msg;
  1739.     const static struct graph def_g = { /* Default values */
  1740.         { NULL },
  1741.         FALSE, TRUE,
  1742.         "Graph",
  1743.         { MENUNULL },
  1744.         { NULL },
  1745.         {
  1746.             {
  1747.                 NOVAL, NOVAL,
  1748.                 NOVAL, NOVAL,
  1749.                 INOVAL,
  1750.                 FALSE
  1751.             },
  1752.             {
  1753.                 NOVAL, NOVAL,
  1754.                 NOVAL, NOVAL,
  1755.                 INOVAL,
  1756.                 FALSE
  1757.             },
  1758.             NOVAL,
  1759.             FALSE, FALSE
  1760.         },
  1761.         {
  1762.             FALSE, FALSE,
  1763.             NOVAL, NOVAL,
  1764.             NULL
  1765.         }
  1766.     };
  1767.  
  1768.     if (g)
  1769.     {
  1770.         *g = def_g;
  1771.         g->io.dpmx = scr_dpmx; g->io.dpmy = scr_dpmy;
  1772.         new_list(&g->o_list); /* no objects */
  1773.         if (g->io.digits = open_font(g, "digits.font", DIGHEIGHT, 0, 0))
  1774.         {
  1775.             if (init_uio(g)) /* Open window */
  1776.             {
  1777.                 set_scale(g);
  1778.                 draw_graph(g, TRUE); /* & display */
  1779.                 return g;
  1780.             }
  1781.             else msg = "No window";
  1782.             CloseFont(g->io.digits);
  1783.         }
  1784.         else msg = "digits.font missing";
  1785.         FreeMem(g, sizeof(struct graph));
  1786.     }
  1787.     else msg = "No memory !";
  1788.  
  1789.     message(from, "Couldn't create graph", msg, (char *)NULL);
  1790.  
  1791.     return NULL;
  1792. }
  1793.  
  1794. /* Global initialisation */
  1795. int init_grph(void)
  1796. {
  1797.     struct Screen wbscr;
  1798.  
  1799.     /* Find screen resolution */
  1800.     scr_dpmx = GfxBase->NormalDPMX;
  1801.     scr_dpmy = GfxBase->NormalDPMY;
  1802.     if (!GetScreenData((char *)&wbscr, sizeof(struct Screen), WBENCHSCREEN, NUL
  1803. L))
  1804.         return alert(NULL, "No Workbench !", NULL), FALSE;
  1805.  
  1806.     if (wbscr.ViewPort.Modes & HIRES) scr_dpmx *= 2;
  1807.     if (wbscr.ViewPort.Modes & LACE) scr_dpmy *= 2;
  1808.  
  1809.     /* Color map for printer. Add colours ? */
  1810.     if (!(prtcm = GetColorMap(4))) return nomem(NULL), FALSE;
  1811.     SetRGB4CM(prtcm, 0, 15, 15, 15);
  1812.     SetRGB4CM(prtcm, 1, 0, 0, 0);
  1813.     SetRGB4CM(prtcm, 2, 0, 0, 0);
  1814.     SetRGB4CM(prtcm, 3, 0, 0, 0);
  1815.  
  1816.     return TRUE;
  1817. }
  1818.  
  1819. /* Free any global resources */
  1820. void cleanup_grph(void)
  1821. {
  1822.     if (prtcm) FreeColorMap(prtcm);
  1823. }
  1824.  
  1825.