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