1/*
2 * tkUnix3d.c --
3 *
4 *	This file contains the platform specific routines for drawing 3d
5 *	borders in the Motif style.
6 *
7 * Copyright (c) 1996 by Sun Microsystems, Inc.
8 *
9 * See the file "license.terms" for information on usage and redistribution of
10 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
11 *
12 * RCS: @(#) $Id$
13 */
14
15#include "tkInt.h"
16#include "tk3d.h"
17
18#if !(defined(__WIN32__) || defined(MAC_OSX_TK))
19#include "tkUnixInt.h"
20#endif
21
22/*
23 * This structure is used to keep track of the extra colors used by Unix 3D
24 * borders.
25 */
26
27typedef struct {
28    TkBorder info;
29    GC solidGC;		/* Used to draw solid relief. */
30} UnixBorder;
31
32/*
33 *----------------------------------------------------------------------
34 *
35 * TkpGetBorder --
36 *
37 *	This function allocates a new TkBorder structure.
38 *
39 * Results:
40 *	Returns a newly allocated TkBorder.
41 *
42 * Side effects:
43 *	None.
44 *
45 *----------------------------------------------------------------------
46 */
47
48TkBorder *
49TkpGetBorder(void)
50{
51    UnixBorder *borderPtr = (UnixBorder *) ckalloc(sizeof(UnixBorder));
52    borderPtr->solidGC = None;
53    return (TkBorder *) borderPtr;
54}
55
56/*
57 *----------------------------------------------------------------------
58 *
59 * TkpFreeBorder --
60 *
61 *	This function frees any colors allocated by the platform specific part
62 *	of this module.
63 *
64 * Results:
65 *	None.
66 *
67 * Side effects:
68 *	May deallocate some colors.
69 *
70 *----------------------------------------------------------------------
71 */
72
73void
74TkpFreeBorder(
75    TkBorder *borderPtr)
76{
77    UnixBorder *unixBorderPtr = (UnixBorder *) borderPtr;
78    Display *display = DisplayOfScreen(borderPtr->screen);
79
80    if (unixBorderPtr->solidGC != None) {
81	Tk_FreeGC(display, unixBorderPtr->solidGC);
82    }
83}
84/*
85 *--------------------------------------------------------------
86 *
87 * Tk_3DVerticalBevel --
88 *
89 *	This procedure draws a vertical bevel along one side of an object. The
90 *	bevel is always rectangular in shape:
91 *			|||
92 *			|||
93 *			|||
94 *			|||
95 *			|||
96 *			|||
97 *	An appropriate shadow color is chosen for the bevel based on the
98 *	leftBevel and relief arguments. Normally this procedure is called
99 *	first, then Tk_3DHorizontalBevel is called next to draw neat corners.
100 *
101 * Results:
102 *	None.
103 *
104 * Side effects:
105 *	Graphics are drawn in drawable.
106 *
107 *--------------------------------------------------------------
108 */
109
110void
111Tk_3DVerticalBevel(
112    Tk_Window tkwin,		/* Window for which border was allocated. */
113    Drawable drawable,		/* X window or pixmap in which to draw. */
114    Tk_3DBorder border,		/* Token for border to draw. */
115    int x, int y, int width, int height,
116				/* Area of vertical bevel. */
117    int leftBevel,		/* Non-zero means this bevel forms the left
118				 * side of the object; 0 means it forms the
119				 * right side. */
120    int relief)			/* Kind of bevel to draw. For example,
121				 * TK_RELIEF_RAISED means interior of object
122				 * should appear higher than exterior. */
123{
124    TkBorder *borderPtr = (TkBorder *) border;
125    GC left, right;
126    Display *display = Tk_Display(tkwin);
127
128    if ((borderPtr->lightGC == None) && (relief != TK_RELIEF_FLAT)) {
129	TkpGetShadows(borderPtr, tkwin);
130    }
131
132    if (relief == TK_RELIEF_RAISED) {
133	XFillRectangle(display, drawable,
134		(leftBevel) ? borderPtr->lightGC : borderPtr->darkGC,
135		x, y, (unsigned) width, (unsigned) height);
136    } else if (relief == TK_RELIEF_SUNKEN) {
137	XFillRectangle(display, drawable,
138		(leftBevel) ? borderPtr->darkGC : borderPtr->lightGC,
139		x, y, (unsigned) width, (unsigned) height);
140    } else if (relief == TK_RELIEF_RIDGE) {
141	int half;
142
143	left = borderPtr->lightGC;
144	right = borderPtr->darkGC;
145    ridgeGroove:
146	half = width/2;
147	if (!leftBevel && (width & 1)) {
148	    half++;
149	}
150	XFillRectangle(display, drawable, left, x, y, (unsigned) half,
151		(unsigned) height);
152	XFillRectangle(display, drawable, right, x+half, y,
153		(unsigned) (width-half), (unsigned) height);
154    } else if (relief == TK_RELIEF_GROOVE) {
155	left = borderPtr->darkGC;
156	right = borderPtr->lightGC;
157	goto ridgeGroove;
158    } else if (relief == TK_RELIEF_FLAT) {
159	XFillRectangle(display, drawable, borderPtr->bgGC, x, y,
160		(unsigned) width, (unsigned) height);
161    } else if (relief == TK_RELIEF_SOLID) {
162	UnixBorder *unixBorderPtr = (UnixBorder *) borderPtr;
163	if (unixBorderPtr->solidGC == None) {
164	    XGCValues gcValues;
165
166	    gcValues.foreground = BlackPixelOfScreen(borderPtr->screen);
167	    unixBorderPtr->solidGC = Tk_GetGC(tkwin, GCForeground, &gcValues);
168	}
169	XFillRectangle(display, drawable, unixBorderPtr->solidGC, x, y,
170		(unsigned) width, (unsigned) height);
171    }
172}
173
174/*
175 *--------------------------------------------------------------
176 *
177 * Tk_3DHorizontalBevel --
178 *
179 *	This procedure draws a horizontal bevel along one side of an object.
180 *	The bevel has mitered corners (depending on leftIn and rightIn
181 *	arguments).
182 *
183 * Results:
184 *	None.
185 *
186 * Side effects:
187 *	None.
188 *
189 *--------------------------------------------------------------
190 */
191
192void
193Tk_3DHorizontalBevel(
194    Tk_Window tkwin,		/* Window for which border was allocated. */
195    Drawable drawable,		/* X window or pixmap in which to draw. */
196    Tk_3DBorder border,		/* Token for border to draw. */
197    int x, int y, int width, int height,
198				/* Bounding box of area of bevel. Height gives
199				 * width of border. */
200    int leftIn, int rightIn,	/* Describes whether the left and right edges
201				 * of the bevel angle in or out as they go
202				 * down. For example, if "leftIn" is true, the
203				 * left side of the bevel looks like this:
204				 *	___________
205				 *	 __________
206				 *	  _________
207				 *	   ________
208				 */
209    int topBevel,		/* Non-zero means this bevel forms the top
210				 * side of the object; 0 means it forms the
211				 * bottom side. */
212    int relief)			/* Kind of bevel to draw. For example,
213				 * TK_RELIEF_RAISED means interior of object
214				 * should appear higher than exterior. */
215{
216    TkBorder *borderPtr = (TkBorder *) border;
217    Display *display = Tk_Display(tkwin);
218    int bottom, halfway, x1, x2, x1Delta, x2Delta;
219    UnixBorder *unixBorderPtr = (UnixBorder *) borderPtr;
220    GC topGC = None, bottomGC = None;
221				/* Initializations needed only to prevent
222				 * compiler warnings. */
223
224    if ((borderPtr->lightGC == None) && (relief != TK_RELIEF_FLAT) &&
225	    (relief != TK_RELIEF_SOLID)) {
226	TkpGetShadows(borderPtr, tkwin);
227    }
228
229    /*
230     * Compute a GC for the top half of the bevel and a GC for the bottom half
231     * (they're the same in many cases).
232     */
233
234    switch (relief) {
235    case TK_RELIEF_FLAT:
236	topGC = bottomGC = borderPtr->bgGC;
237	break;
238    case TK_RELIEF_GROOVE:
239	topGC = borderPtr->darkGC;
240	bottomGC = borderPtr->lightGC;
241	break;
242    case TK_RELIEF_RAISED:
243	topGC = bottomGC = (topBevel? borderPtr->lightGC : borderPtr->darkGC);
244	break;
245    case TK_RELIEF_RIDGE:
246	topGC = borderPtr->lightGC;
247	bottomGC = borderPtr->darkGC;
248	break;
249    case TK_RELIEF_SOLID:
250	if (unixBorderPtr->solidGC == None) {
251	    XGCValues gcValues;
252
253	    gcValues.foreground = BlackPixelOfScreen(borderPtr->screen);
254	    unixBorderPtr->solidGC = Tk_GetGC(tkwin, GCForeground, &gcValues);
255	}
256	XFillRectangle(display, drawable, unixBorderPtr->solidGC, x, y,
257		(unsigned) width, (unsigned) height);
258	return;
259    case TK_RELIEF_SUNKEN:
260	topGC = bottomGC = (topBevel? borderPtr->darkGC : borderPtr->lightGC);
261	break;
262    }
263
264    /*
265     * Compute various other geometry-related stuff.
266     */
267
268    x1 = x;
269    if (!leftIn) {
270	x1 += height;
271    }
272    x2 = x+width;
273    if (!rightIn) {
274	x2 -= height;
275    }
276    x1Delta = (leftIn) ? 1 : -1;
277    x2Delta = (rightIn) ? -1 : 1;
278    halfway = y + height/2;
279    if (!topBevel && (height & 1)) {
280	halfway++;
281    }
282    bottom = y + height;
283
284    /*
285     * Draw one line for each y-coordinate covered by the bevel.
286     */
287
288    for ( ; y < bottom; y++) {
289	/*
290	 * X Dimensions are 16-bit, so avoid wraparound or display errors by
291	 * limiting these here.
292	 */
293
294	if (x1 < -32767) {
295	    x1 = -32767;
296	}
297	if (x2 > 32767) {
298	    x2 = 32767;
299	}
300
301	/*
302	 * In some weird cases (such as large border widths for skinny
303	 * rectangles) x1 can be >= x2. Don't draw the lines in these cases.
304	 */
305
306	if (x1 < x2) {
307	    XFillRectangle(display, drawable,
308		    (y < halfway) ? topGC : bottomGC, x1, y,
309		    (unsigned) (x2-x1), (unsigned) 1);
310	}
311	x1 += x1Delta;
312	x2 += x2Delta;
313    }
314}
315
316/*
317 *----------------------------------------------------------------------
318 *
319 * TkpGetShadows --
320 *
321 *	This procedure computes the shadow colors for a 3-D border and fills
322 *	in the corresponding fields of the Border structure. It's called
323 *	lazily, so that the colors aren't allocated until something is
324 *	actually drawn with them. That way, if a border is only used for flat
325 *	backgrounds the shadow colors will never be allocated.
326 *
327 * Results:
328 *	None.
329 *
330 * Side effects:
331 *	The lightGC and darkGC fields in borderPtr get filled in, if they
332 *	weren't already.
333 *
334 *----------------------------------------------------------------------
335 */
336
337void
338TkpGetShadows(
339    TkBorder *borderPtr,	/* Information about border. */
340    Tk_Window tkwin)		/* Window where border will be used for
341				 * drawing. */
342{
343    XColor lightColor, darkColor;
344    int stressed, tmp1, tmp2;
345    int r, g, b;
346    XGCValues gcValues;
347
348    if (borderPtr->lightGC != None) {
349	return;
350    }
351    stressed = TkpCmapStressed(tkwin, borderPtr->colormap);
352
353    /*
354     * First, handle the case of a color display with lots of colors. The
355     * shadow colors get computed using whichever formula results in the
356     * greatest change in color:
357     * 1. Lighter shadow is half-way to white, darker shadow is half way to
358     *    dark.
359     * 2. Lighter shadow is 40% brighter than background, darker shadow is 40%
360     *    darker than background.
361     */
362
363    if (!stressed && (Tk_Depth(tkwin) >= 6)) {
364	/*
365	 * This is a color display with lots of colors. For the dark shadow,
366	 * cut 40% from each of the background color components. But if the
367	 * background is already very dark, make the dark color a little
368	 * lighter than the background by increasing each color component
369	 * 1/4th of the way to MAX_INTENSITY.
370	 *
371	 * For the light shadow, boost each component by 40% or half-way to
372	 * white, whichever is greater (the first approach works better for
373	 * unsaturated colors, the second for saturated ones). But if the
374	 * background is already very bright, instead choose a slightly darker
375	 * color for the light shadow by reducing each color component by 10%.
376	 *
377	 * Compute the colors using integers, not using lightColor.red etc.:
378	 * these are shorts and may have problems with integer overflow.
379	 */
380
381	/*
382	 * Compute the dark shadow color
383	 */
384
385	r = (int) borderPtr->bgColorPtr->red;
386	g = (int) borderPtr->bgColorPtr->green;
387	b = (int) borderPtr->bgColorPtr->blue;
388
389	if (r*0.5*r + g*1.0*g + b*0.28*b < MAX_INTENSITY*0.05*MAX_INTENSITY) {
390	    darkColor.red = (MAX_INTENSITY + 3*r)/4;
391	    darkColor.green = (MAX_INTENSITY + 3*g)/4;
392	    darkColor.blue = (MAX_INTENSITY + 3*b)/4;
393	} else {
394	    darkColor.red = (60 * r)/100;
395	    darkColor.green = (60 * g)/100;
396	    darkColor.blue = (60 * b)/100;
397	}
398
399	/*
400	 * Allocate the dark shadow color and its GC
401	 */
402
403	borderPtr->darkColorPtr = Tk_GetColorByValue(tkwin, &darkColor);
404	gcValues.foreground = borderPtr->darkColorPtr->pixel;
405	borderPtr->darkGC = Tk_GetGC(tkwin, GCForeground, &gcValues);
406
407	/*
408	 * Compute the light shadow color
409	 */
410
411	if (g > MAX_INTENSITY*0.95) {
412	    lightColor.red = (90 * r)/100;
413	    lightColor.green = (90 * g)/100;
414	    lightColor.blue = (90 * b)/100;
415	} else {
416	    tmp1 = (14 * r)/10;
417	    if (tmp1 > MAX_INTENSITY) {
418		tmp1 = MAX_INTENSITY;
419	    }
420	    tmp2 = (MAX_INTENSITY + r)/2;
421	    lightColor.red = (tmp1 > tmp2) ? tmp1 : tmp2;
422	    tmp1 = (14 * g)/10;
423	    if (tmp1 > MAX_INTENSITY) {
424		tmp1 = MAX_INTENSITY;
425	    }
426	    tmp2 = (MAX_INTENSITY + g)/2;
427	    lightColor.green = (tmp1 > tmp2) ? tmp1 : tmp2;
428	    tmp1 = (14 * b)/10;
429	    if (tmp1 > MAX_INTENSITY) {
430		tmp1 = MAX_INTENSITY;
431	    }
432	    tmp2 = (MAX_INTENSITY + b)/2;
433	    lightColor.blue = (tmp1 > tmp2) ? tmp1 : tmp2;
434	}
435
436       /*
437        * Allocate the light shadow color and its GC
438        */
439
440	borderPtr->lightColorPtr = Tk_GetColorByValue(tkwin, &lightColor);
441	gcValues.foreground = borderPtr->lightColorPtr->pixel;
442	borderPtr->lightGC = Tk_GetGC(tkwin, GCForeground, &gcValues);
443	return;
444    }
445
446    if (borderPtr->shadow == None) {
447	borderPtr->shadow = Tk_GetBitmap((Tcl_Interp *) NULL, tkwin,
448		Tk_GetUid("gray50"));
449	if (borderPtr->shadow == None) {
450	    Tcl_Panic("TkpGetShadows couldn't allocate bitmap for border");
451	}
452    }
453    if (borderPtr->visual->map_entries > 2) {
454	/*
455	 * This isn't a monochrome display, but the colormap either ran out of
456	 * entries or didn't have very many to begin with. Generate the light
457	 * shadows with a white stipple and the dark shadows with a black
458	 * stipple.
459	 */
460
461	gcValues.foreground = borderPtr->bgColorPtr->pixel;
462	gcValues.background = BlackPixelOfScreen(borderPtr->screen);
463	gcValues.stipple = borderPtr->shadow;
464	gcValues.fill_style = FillOpaqueStippled;
465	borderPtr->darkGC = Tk_GetGC(tkwin,
466		GCForeground|GCBackground|GCStipple|GCFillStyle, &gcValues);
467	gcValues.background = WhitePixelOfScreen(borderPtr->screen);
468	borderPtr->lightGC = Tk_GetGC(tkwin,
469		GCForeground|GCBackground|GCStipple|GCFillStyle, &gcValues);
470	return;
471    }
472
473    /*
474     * This is just a measly monochrome display, hardly even worth its
475     * existence on this earth. Make one shadow a 50% stipple and the other
476     * the opposite of the background.
477     */
478
479    gcValues.foreground = WhitePixelOfScreen(borderPtr->screen);
480    gcValues.background = BlackPixelOfScreen(borderPtr->screen);
481    gcValues.stipple = borderPtr->shadow;
482    gcValues.fill_style = FillOpaqueStippled;
483    borderPtr->lightGC = Tk_GetGC(tkwin,
484	    GCForeground|GCBackground|GCStipple|GCFillStyle, &gcValues);
485    if (borderPtr->bgColorPtr->pixel
486	    == WhitePixelOfScreen(borderPtr->screen)) {
487	gcValues.foreground = BlackPixelOfScreen(borderPtr->screen);
488	borderPtr->darkGC = Tk_GetGC(tkwin, GCForeground, &gcValues);
489    } else {
490	borderPtr->darkGC = borderPtr->lightGC;
491	borderPtr->lightGC = Tk_GetGC(tkwin, GCForeground, &gcValues);
492    }
493}
494
495/*
496 * Local Variables:
497 * mode: c
498 * c-basic-offset: 4
499 * fill-column: 78
500 * End:
501 */
502