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