| /* | |
| * GraphApp - Cross-Platform Graphics Programming Library. | |
| * | |
| * File: context.c -- internal functions for manipulating DCs. | |
| * Platform: Windows Version: 2.35 Date: 1998/04/04 | |
| * | |
| * Version: 1.00 Changes: Original version by Lachlan Patrick. | |
| * Version: 1.05 Changes: Added drawstate information. | |
| * Version: 1.50 Changes: New colour system, dithered grey. | |
| * Version: 2.00 Changes: New object class and context sub-system. | |
| * Version: 2.01 Changes: get_context is now more bulletproof. | |
| * Version: 2.02 Changes: drawto and setwinrgb have been updated. | |
| * Version: 2.20 Changes: Added currentrgb, currentfont etc. | |
| * Version: 2.35 Changes: New reference count technique. | |
| */ | |
| /* Copyright (C) 1993-1998 Lachlan Patrick | |
| This file is part of GraphApp, a cross-platform C graphics library. | |
| GraphApp is free software; you can redistribute it and/or modify it | |
| under the terms of the GNU Library General Public License. | |
| GraphApp is distributed in the hope that it will be useful, but | |
| WITHOUT ANY WARRANTY. | |
| See the file COPYLIB.TXT for details. | |
| */ | |
| /* Copyright (C) 2004 The R Foundation | |
| Changes for R: allow 32 contexts not 4 | |
| Remove assumption that current->dest is non-NULL | |
| */ | |
| #include "internal.h" | |
| /* | |
| * Private library variables. | |
| */ | |
| PROTECTED drawstruct app_drawstate = | |
| { | |
| NULL, /* drawing destination */ | |
| Black, /* colour */ | |
| S, /* drawing mode */ | |
| {0,0}, /* point */ | |
| 1, /* line width */ | |
| NULL, /* font */ | |
| NULL, /* cursor */ | |
| }; | |
| PROTECTED drawstate current = & app_drawstate; | |
| PROTECTED HDC dc; /* shared DC variable */ | |
| PROTECTED HPEN the_pen = 0; | |
| PROTECTED HBRUSH the_brush = 0; | |
| PROTECTED COLORREF win_rgb = 0L; | |
| /* | |
| * Private drawing context variables. | |
| */ | |
| static rgb prev_pixval = Black; | |
| static int prev_width = 1; | |
| static bitmap grey_bitmap = NULL; | |
| static rgb grey_cmap [] = { | |
| 0x00000000UL, | |
| 0x00FFFFFFUL, | |
| }; | |
| static byte grey_pixels [] = { | |
| 0, 1, 0, 1, 0, 1, 0, 1, | |
| 1, 0, 1, 0, 1, 0, 1, 0, | |
| 0, 1, 0, 1, 0, 1, 0, 1, | |
| 1, 0, 1, 0, 1, 0, 1, 0, | |
| 0, 1, 0, 1, 0, 1, 0, 1, | |
| 1, 0, 1, 0, 1, 0, 1, 0, | |
| 0, 1, 0, 1, 0, 1, 0, 1, | |
| 1, 0, 1, 0, 1, 0, 1, 0, | |
| }; | |
| static imagedata grey_imagedata = { | |
| 8, /* depth */ | |
| 8, /* width */ | |
| 8, /* height */ | |
| 2, /* cmapsize */ | |
| grey_cmap, | |
| grey_pixels | |
| }; | |
| static image grey_image = & grey_imagedata; | |
| /* | |
| * Private context list structure | |
| */ | |
| typedef struct contextinfo contextinfo; | |
| struct contextinfo | |
| { | |
| object obj; /* back pointer to drawing object */ | |
| HDC dc; /* handle to DC used when drawing */ | |
| HGDIOBJ old_bitmap; /* bitmap returned by SelectObject() */ | |
| }; | |
| /* | |
| * The constant MAX_CONTEXTS controls how large the | |
| * circular array of contexts is, and thus how many | |
| * concurrently active contexts there can be. This | |
| * number should be at least 2, since bitblt needs | |
| * two contexts for source and destination bitmaps. | |
| */ | |
| #define MAX_CONTEXTS 32 | |
| static contextinfo context[MAX_CONTEXTS]; | |
| static int num_contexts = 0; | |
| static int empty_slot = 0; | |
| /* | |
| * Create a pen and a brush for use in drawing. | |
| */ | |
| static void create_pen_and_brush(rgb c, unsigned long winrgb, | |
| int width, int depth) | |
| { | |
| the_pen = CreatePen(PS_INSIDEFRAME, width, winrgb); | |
| if ((depth == 1) && (c == Grey)) | |
| the_brush = CreatePatternBrush(grey_bitmap->handle); | |
| else | |
| the_brush = CreateSolidBrush(winrgb); | |
| } | |
| /* | |
| * Delete any created pens and brushes. | |
| * Assumes they are not selected into any valid DC. | |
| */ | |
| static void delete_pen_and_brush(void) | |
| { | |
| DeleteObject(the_pen); | |
| DeleteObject(the_brush); | |
| } | |
| /* | |
| * Set Windows drawing globals up. | |
| */ | |
| PROTECTED | |
| void init_contexts(void) | |
| { | |
| grey_bitmap = imagetobitmap(grey_image); | |
| grey_bitmap->text = new_string("grey_bitmap"); | |
| current = & app_drawstate; | |
| app_drawstate.fnt = SystemFont; | |
| app_drawstate.crsr = ArrowCursor; | |
| create_pen_and_brush(Black, Black, 1, 0); | |
| } | |
| static void free_context(contextinfo *c) | |
| { | |
| if (! c->dc) | |
| return; /* already been deleted */ | |
| SelectObject(c->dc, GetStockObject(NULL_PEN)); | |
| SelectObject(c->dc, GetStockObject(NULL_BRUSH)); | |
| if (c->obj->kind & ControlObject) | |
| ReleaseDC(c->obj->handle, c->dc); | |
| else if (c->obj->kind == BitmapObject) { | |
| SelectObject(c->dc, c->old_bitmap); | |
| DeleteDC(c->dc); | |
| } | |
| if (dc == c->dc) | |
| dc = 0; | |
| c->dc = 0; | |
| c->obj = NULL; | |
| c->old_bitmap = 0; | |
| } | |
| /* | |
| * Add a new DC into our list of DCs. This is kind of ugly: num_contexts just keeps growing, unless | |
| * del_all_contexts is called, when it is set to 0. The free ones will be scattered through the list. | |
| */ | |
| PROTECTED | |
| void add_context(object obj, HDC dc, HGDIOBJ old) | |
| { | |
| contextinfo *c; | |
| /* Search for or clear a spot for the new DC if the array is full. */ | |
| if (num_contexts == MAX_CONTEXTS) { | |
| int i = empty_slot; | |
| while ( context[i].dc ) { | |
| i = (i+1) % MAX_CONTEXTS; | |
| if (i == empty_slot) { | |
| free_context(&context[i]); | |
| break; | |
| } | |
| } | |
| c = & context[i]; | |
| empty_slot = (i+1) % MAX_CONTEXTS; | |
| } else { | |
| c = & context[num_contexts]; | |
| num_contexts++; | |
| } | |
| /* Add this context to the list. */ | |
| c->obj = obj; | |
| c->dc = dc; | |
| c->old_bitmap = old; | |
| } | |
| /* | |
| * Find and return a DC for a window or a bitmap. | |
| * Also keep track of which DCs have been created. | |
| */ | |
| PROTECTED | |
| HDC get_context(object obj) | |
| { | |
| int i; | |
| HDC dc; | |
| HGDIOBJ old; | |
| /* Determine if a DC for this object already exists. */ | |
| for (i = 0; i < num_contexts; i++) { | |
| if (context[i].obj == obj) | |
| return context[i].dc; | |
| } | |
| /* Use GetDC or CreateCompatibleDC to return a DC. */ | |
| if (obj->kind & ControlObject) { | |
| dc = GetDC(obj->handle); | |
| add_context(obj, dc, 0); | |
| } | |
| else if (obj->kind == BitmapObject) { | |
| dc = CreateCompatibleDC(0); | |
| old = SelectObject(dc, obj->handle); | |
| add_context(obj, dc, old); | |
| } | |
| else { | |
| return NULL; | |
| /* apperror("Cannot find DC for non-drawable object."); */ | |
| } | |
| /* We only select in the brush or pen we need, when we | |
| * need it. Thus at all other times, they are NULL. | |
| * This ensures we can delete objects when we want, and | |
| * also makes correct context possible. */ | |
| SelectObject(dc, GetStockObject(NULL_PEN)); | |
| SelectObject(dc, GetStockObject(NULL_BRUSH)); | |
| return dc; | |
| } | |
| /* | |
| * Remove the context from the list by blanking its fields. | |
| * It is assumed that the DC has previously been released, | |
| * for example by EndPaint() in the events.c file. | |
| */ | |
| PROTECTED | |
| void remove_context(object obj) | |
| { | |
| int i; | |
| contextinfo *c; | |
| for (i = 0; i < num_contexts; i++) { | |
| c = & context[i]; | |
| if (c->obj == obj) { | |
| c->obj = NULL; | |
| c->dc = 0; | |
| c->old_bitmap = 0; | |
| /* If we delete the last one, reduce the count: avoid | |
| a search next time. */ | |
| if (i == num_contexts - 1) | |
| num_contexts--; | |
| else | |
| empty_slot = i; | |
| } | |
| } | |
| } | |
| /* | |
| * Free the DC associated with a given object. | |
| */ | |
| PROTECTED | |
| void del_context(object obj) | |
| { | |
| int i; | |
| contextinfo *c; | |
| for (i = 0; i < num_contexts; i++) { | |
| c = & context[i]; | |
| if (c->obj == obj) { | |
| free_context(c); | |
| c->obj = NULL; | |
| c->dc = 0; | |
| c->old_bitmap = 0; | |
| /* If we delete the last one, reduce the count: avoid | |
| a search next time. */ | |
| if (i == num_contexts - 1) | |
| num_contexts--; | |
| else | |
| empty_slot = i; | |
| } | |
| } | |
| } | |
| /* | |
| * Get rid of all DCs. | |
| */ | |
| PROTECTED | |
| void del_all_contexts(void) | |
| { | |
| int i; | |
| contextinfo *c; | |
| for (i = 0; i < num_contexts; i++) { | |
| c = & context[i]; | |
| free_context(c); | |
| c->obj = NULL; | |
| c->dc = 0; | |
| c->old_bitmap = 0; | |
| } | |
| num_contexts = 0; | |
| empty_slot = 0; | |
| } | |
| /* | |
| * De-initialise drawing variables. | |
| */ | |
| PROTECTED | |
| void finish_contexts(void) | |
| { | |
| del_all_contexts(); | |
| DeleteObject(the_pen); | |
| DeleteObject(the_brush); | |
| } | |
| /* | |
| * Set up a pen and a brush for use in colouring things, and return | |
| * the Windows RGB value equivalent to the library rgb value. | |
| */ | |
| static unsigned long set_win_rgb(rgb c, int width) | |
| { | |
| int r, g, b; | |
| long luminance; | |
| int depth; | |
| unsigned long winrgb; | |
| if (current->mode == Ones) | |
| c = White; | |
| else if (current->mode == Zeros) | |
| c = Black; | |
| r = (int) ((c >> 16) & 0x000000FFL); | |
| g = (int) ((c >> 8) & 0x000000FFL); | |
| b = (int) ((c >> 0) & 0x000000FFL); | |
| if (current->dest) depth = getdepth(current->dest); | |
| else depth = 2; /* set default minimal depth if no current window */ | |
| if (depth <= 2) /* map to black or white, or grey if c == Grey */ | |
| { | |
| luminance = (r*3 + g*5 + b) / 9; | |
| if (luminance > 0x0087) r = g = b = 0x00FF; | |
| else if (luminance <= 0x0077) r = g = b = 0x0000; | |
| else r = g = b = 0x0080; | |
| c = rgb(r,g,b); | |
| } | |
| winrgb = RGB(r, g, b); | |
| /* Has a colour or width change occured? */ | |
| if ((c != prev_pixval) || (width != prev_width)) | |
| { | |
| prev_pixval = c; | |
| prev_width = width; | |
| /* delete any old objects */ | |
| delete_pen_and_brush(); | |
| /* set up new objects */ | |
| create_pen_and_brush(c, winrgb, width, depth); | |
| } | |
| return winrgb; | |
| } | |
| void setcursor(cursor c) | |
| { | |
| decrease_refcount(current->crsr); | |
| current->crsr = c; | |
| if (c) SetCursor((HCURSOR) c->handle); | |
| increase_refcount(c); | |
| } | |
| void setfont(font f) | |
| { | |
| decrease_refcount(current->fnt); | |
| current->fnt = f; | |
| increase_refcount(f); | |
| } | |
| /* | |
| * Set the way that source and destination pixels are combined | |
| * when drawing. | |
| */ | |
| void setdrawmode(int mode) | |
| { | |
| current->mode = mode & 0x0F; /* must be between 0x00 and 0x0F */ | |
| } | |
| /* | |
| * Set the colour. | |
| */ | |
| void setrgb(rgb hue) | |
| { | |
| current->hue = hue; | |
| win_rgb = set_win_rgb(hue, current->linewidth); | |
| } | |
| /* | |
| * Set the line width. | |
| */ | |
| void setlinewidth(int width) | |
| { | |
| if (width < 1) | |
| width = 1; | |
| current->linewidth = width; | |
| win_rgb = set_win_rgb(current->hue, current->linewidth); | |
| } | |
| /* | |
| * Set which window/menubar/menu to add new objects too. | |
| */ | |
| void addto(object obj) | |
| { | |
| if (! obj) | |
| return; | |
| switch (obj->kind) { | |
| case WindowObject: current_window = obj; break; | |
| case MenubarObject: current_menubar = obj; break; | |
| case MenuObject: current_menu = obj; break; | |
| } | |
| } | |
| /* | |
| * Set which bitmap or window to draw to; allocate a DC too. | |
| */ | |
| void drawto(drawing d) | |
| { | |
| if (! d) { | |
| current = & app_drawstate; | |
| current->dest = NULL; | |
| return; | |
| } | |
| dc = get_context(d); | |
| if (d->drawstate) { | |
| /* Change the current drawing state to this one. */ | |
| current = d->drawstate; | |
| current->dest = d; | |
| win_rgb = set_win_rgb(current->hue, current->linewidth); | |
| } | |
| else { | |
| /* Otherwise just use the current drawing state. */ | |
| current->dest = d; | |
| } | |
| } | |
| /* | |
| * Ensure drawing variables are set up. | |
| */ | |
| void setdrawstate(drawstate s) | |
| { | |
| if (! s) | |
| return; | |
| moveto(s->p); | |
| setdrawmode(s->mode); | |
| setcursor(s->crsr); | |
| setfont(s->fnt); | |
| setlinewidth(s->linewidth); | |
| setrgb(s->hue); | |
| } | |
| /* | |
| * Change the current drawstate to the specified one. | |
| */ | |
| void restoredrawstate(drawstate s) | |
| { | |
| if (! s) | |
| return; | |
| setdrawstate(s); | |
| discard(s); | |
| } | |
| /* | |
| * Reset drawing variables to initial values. | |
| */ | |
| void resetdrawstate(void) | |
| { | |
| setrgb(Black); | |
| setlinewidth(1); | |
| setcursor(ArrowCursor); | |
| moveto(pt(0,0)); | |
| setfont(SystemFont); | |
| setdrawmode(S); | |
| } | |
| /* | |
| * Return a new copy of the current drawing state. | |
| */ | |
| drawstate copydrawstate(void) | |
| { | |
| drawstate s = NULL; | |
| if (current) { | |
| s = create(drawstruct); | |
| if (s) | |
| *s = *current; | |
| } | |
| return s; | |
| } | |
| /* | |
| * Return drawing state information. | |
| */ | |
| drawing currentdrawing(void) { return current->dest; } | |
| rgb currentrgb(void) { return current->hue; } | |
| int currentmode(void) { return current->mode; } | |
| point currentpoint(void) { return current->p; } | |
| int currentlinewidth(void) { return current->linewidth; } | |
| font currentfont(void) { return current->fnt; } | |
| cursor currentcursor(void) { return current->crsr; } | |
| int getkeystate(void) { return keystate; } |