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