1 /*
2  * tkWinColor.c --
3  *
4  *	Functions to map color names to system color values.
5  *
6  * Copyright (c) 1995 Sun Microsystems, Inc.
7  * Copyright (c) 1994 Software Research Associates, 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 
13 #include "tkWinInt.h"
14 #include "tkColor.h"
15 
16 /*
17  * The following structure is used to keep track of each color that is
18  * allocated by this module.
19  */
20 
21 typedef struct WinColor {
22     TkColor info;		/* Generic color information. */
23     int index;			/* Index for GetSysColor(), -1 if color is not
24 				 * a "live" system color. */
25 } WinColor;
26 
27 /*
28  * The sysColors array contains the names and index values for the Windows
29  * indirect system color names. In use, all of the names will have the string
30  * "System" prepended, but we omit it in the table to save space.
31  */
32 
33 typedef struct {
34     char *name;
35     int index;
36 } SystemColorEntry;
37 
38 static SystemColorEntry sysColors[] = {
39     {"3dDarkShadow",		COLOR_3DDKSHADOW},
40     {"3dLight",			COLOR_3DLIGHT},
41     {"ActiveBorder",		COLOR_ACTIVEBORDER},
42     {"ActiveCaption",		COLOR_ACTIVECAPTION},
43     {"AppWorkspace",		COLOR_APPWORKSPACE},
44     {"Background",		COLOR_BACKGROUND},
45     {"ButtonFace",		COLOR_BTNFACE},
46     {"ButtonHighlight",		COLOR_BTNHIGHLIGHT},
47     {"ButtonShadow",		COLOR_BTNSHADOW},
48     {"ButtonText",		COLOR_BTNTEXT},
49     {"CaptionText",		COLOR_CAPTIONTEXT},
50     {"DisabledText",		COLOR_GRAYTEXT},
51     {"GrayText",		COLOR_GRAYTEXT},
52     {"Highlight",		COLOR_HIGHLIGHT},
53     {"HighlightText",		COLOR_HIGHLIGHTTEXT},
54     {"InactiveBorder",		COLOR_INACTIVEBORDER},
55     {"InactiveCaption",		COLOR_INACTIVECAPTION},
56     {"InactiveCaptionText",	COLOR_INACTIVECAPTIONTEXT},
57     {"InfoBackground",		COLOR_INFOBK},
58     {"InfoText",		COLOR_INFOTEXT},
59     {"Menu",			COLOR_MENU},
60     {"MenuText",		COLOR_MENUTEXT},
61     {"Scrollbar",		COLOR_SCROLLBAR},
62     {"Window",			COLOR_WINDOW},
63     {"WindowFrame",		COLOR_WINDOWFRAME},
64     {"WindowText",		COLOR_WINDOWTEXT},
65     {NULL,			0}
66 };
67 
68 typedef struct ThreadSpecificData {
69     int ncolors;
70 } ThreadSpecificData;
71 static Tcl_ThreadDataKey dataKey;
72 
73 /*
74  * Forward declarations for functions defined later in this file.
75  */
76 
77 static int		FindSystemColor(const char *name, XColor *colorPtr,
78 			    int *indexPtr);
79 
80 /*
81  *----------------------------------------------------------------------
82  *
83  * FindSystemColor --
84  *
85  *	This routine finds the color entry that corresponds to the specified
86  *	color.
87  *
88  * Results:
89  *	Returns non-zero on success. The RGB values of the XColor will be
90  *	initialized to the proper values on success.
91  *
92  * Side effects:
93  *	None.
94  *
95  *----------------------------------------------------------------------
96  */
97 
98 static int
FindSystemColor(const char * name,XColor * colorPtr,int * indexPtr)99 FindSystemColor(
100     const char *name,		/* Color name. */
101     XColor *colorPtr,		/* Where to store results. */
102     int *indexPtr)		/* Out parameter to store color index. */
103 {
104     int l, u, r, i;
105     ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
106 	    Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
107 
108     /*
109      * Count the number of elements in the color array if we haven't done so
110      * yet.
111      */
112 
113     if (tsdPtr->ncolors == 0) {
114 	SystemColorEntry *ePtr;
115 	int version;
116 
117 	version = LOBYTE(LOWORD(GetVersion()));
118 	for (ePtr = sysColors; ePtr->name != NULL; ePtr++) {
119 	    if (version < 4) {
120 		if (ePtr->index == COLOR_3DDKSHADOW) {
121 		    ePtr->index = COLOR_BTNSHADOW;
122 		} else if (ePtr->index == COLOR_3DLIGHT) {
123 		    ePtr->index = COLOR_BTNHIGHLIGHT;
124 		}
125 	    }
126 	    tsdPtr->ncolors++;
127 	}
128     }
129 
130     /*
131      * Perform a binary search on the sorted array of colors.
132      */
133 
134     l = 0;
135     u = tsdPtr->ncolors - 1;
136     while (l <= u) {
137 	i = (l + u) / 2;
138 	r = strcasecmp(name, sysColors[i].name);
139 	if (r == 0) {
140 	    break;
141 	} else if (r < 0) {
142 	    u = i-1;
143 	} else {
144 	    l = i+1;
145 	}
146     }
147     if (l > u) {
148 	return 0;
149     }
150 
151     *indexPtr = sysColors[i].index;
152     colorPtr->pixel = GetSysColor(sysColors[i].index);
153 
154     /*
155      * x257 is (value<<8 + value) to get the properly bit shifted and padded
156      * value. [Bug: 4919]
157      */
158 
159     colorPtr->red = GetRValue(colorPtr->pixel) * 257;
160     colorPtr->green = GetGValue(colorPtr->pixel) * 257;
161     colorPtr->blue = GetBValue(colorPtr->pixel) * 257;
162     colorPtr->flags = DoRed|DoGreen|DoBlue;
163     colorPtr->pad = 0;
164     return 1;
165 }
166 
167 /*
168  *----------------------------------------------------------------------
169  *
170  * TkpGetColor --
171  *
172  *	Allocate a new TkColor for the color with the given name.
173  *
174  * Results:
175  *	Returns a newly allocated TkColor, or NULL on failure.
176  *
177  * Side effects:
178  *	May invalidate the colormap cache associated with tkwin upon
179  *	allocating a new colormap entry. Allocates a new TkColor structure.
180  *
181  *----------------------------------------------------------------------
182  */
183 
184 TkColor *
TkpGetColor(Tk_Window tkwin,Tk_Uid name)185 TkpGetColor(
186     Tk_Window tkwin,		/* Window in which color will be used. */
187     Tk_Uid name)		/* Name of color to allocated (in form
188 				 * suitable for passing to XParseColor). */
189 {
190     WinColor *winColPtr;
191     XColor color;
192     int index = -1;		/* -1 indicates that this is not an indirect
193 				 * system color. */
194 
195     /*
196      * Check to see if it is a system color or an X color string. If the color
197      * is found, allocate a new WinColor and store the XColor and the system
198      * color index.
199      */
200 
201     if (((strncasecmp(name, "system", 6) == 0)
202 	    && FindSystemColor(name+6, &color, &index))
203 	    || TkParseColor(Tk_Display(tkwin), Tk_Colormap(tkwin), name,
204 		    &color)) {
205 	winColPtr = (WinColor *) ckalloc(sizeof(WinColor));
206 	winColPtr->info.color = color;
207 	winColPtr->index = index;
208 
209 	XAllocColor(Tk_Display(tkwin), Tk_Colormap(tkwin),
210 		&winColPtr->info.color);
211  	return (TkColor *) winColPtr;
212     }
213     return (TkColor *) NULL;
214 }
215 
216 /*
217  *----------------------------------------------------------------------
218  *
219  * TkpGetColorByValue --
220  *
221  *	Given a desired set of red-green-blue intensities for a color, locate
222  *	a pixel value to use to draw that color in a given window.
223  *
224  * Results:
225  *	The return value is a pointer to an TkColor structure that indicates
226  *	the closest red, blue, and green intensities available to those
227  *	specified in colorPtr, and also specifies a pixel value to use to draw
228  *	in that color.
229  *
230  * Side effects:
231  *	May invalidate the colormap cache for the specified window. Allocates
232  *	a new TkColor structure.
233  *
234  *----------------------------------------------------------------------
235  */
236 
237 TkColor *
TkpGetColorByValue(Tk_Window tkwin,XColor * colorPtr)238 TkpGetColorByValue(
239     Tk_Window tkwin,		/* Window in which color will be used. */
240     XColor *colorPtr)		/* Red, green, and blue fields indicate
241 				 * desired color. */
242 {
243     WinColor *tkColPtr = (WinColor *) ckalloc(sizeof(WinColor));
244 
245     tkColPtr->info.color.red = colorPtr->red;
246     tkColPtr->info.color.green = colorPtr->green;
247     tkColPtr->info.color.blue = colorPtr->blue;
248     tkColPtr->info.color.pixel = 0;
249     tkColPtr->index = -1;
250     XAllocColor(Tk_Display(tkwin), Tk_Colormap(tkwin), &tkColPtr->info.color);
251     return (TkColor *) tkColPtr;
252 }
253 
254 /*
255  *----------------------------------------------------------------------
256  *
257  * TkpFreeColor --
258  *
259  *	Release the specified color back to the system.
260  *
261  * Results:
262  *	None
263  *
264  * Side effects:
265  *	Invalidates the colormap cache for the colormap associated with the
266  *	given color.
267  *
268  *----------------------------------------------------------------------
269  */
270 
271 void
TkpFreeColor(TkColor * tkColPtr)272 TkpFreeColor(
273     TkColor *tkColPtr)		/* Color to be released. Must have been
274 				 * allocated by TkpGetColor or
275 				 * TkpGetColorByValue. */
276 {
277     Screen *screen = tkColPtr->screen;
278 
279     XFreeColors(DisplayOfScreen(screen), tkColPtr->colormap,
280 	    &tkColPtr->color.pixel, 1, 0L);
281 }
282 
283 /*
284  *----------------------------------------------------------------------
285  *
286  * TkWinIndexOfColor --
287  *
288  *	Given a color, return the system color index that was used to create
289  *	the color.
290  *
291  * Results:
292  *	If the color was allocated using a system indirect color name, then
293  *	the corresponding GetSysColor() index is returned. Otherwise, -1 is
294  *	returned.
295  *
296  * Side effects:
297  *	None.
298  *
299  *----------------------------------------------------------------------
300  */
301 
302 int
TkWinIndexOfColor(XColor * colorPtr)303 TkWinIndexOfColor(
304     XColor *colorPtr)
305 {
306     register WinColor *winColPtr = (WinColor *) colorPtr;
307     if (winColPtr->info.magic == COLOR_MAGIC) {
308 	return winColPtr->index;
309     }
310     return -1;
311 }
312 
313 /*
314  *----------------------------------------------------------------------
315  *
316  * XAllocColor --
317  *
318  *	Find the closest available color to the specified XColor.
319  *
320  * Results:
321  *	Updates the color argument and returns 1 on success. Otherwise returns
322  *	0.
323  *
324  * Side effects:
325  *	Allocates a new color in the palette.
326  *
327  *----------------------------------------------------------------------
328  */
329 
330 int
XAllocColor(Display * display,Colormap colormap,XColor * color)331 XAllocColor(
332     Display *display,
333     Colormap colormap,
334     XColor *color)
335 {
336     TkWinColormap *cmap = (TkWinColormap *) colormap;
337     PALETTEENTRY entry, closeEntry;
338     HDC dc = GetDC(NULL);
339 
340     entry.peRed = (color->red) >> 8;
341     entry.peGreen = (color->green) >> 8;
342     entry.peBlue = (color->blue) >> 8;
343     entry.peFlags = 0;
344 
345     if (GetDeviceCaps(dc, RASTERCAPS) & RC_PALETTE) {
346 	unsigned long sizePalette = GetDeviceCaps(dc, SIZEPALETTE);
347 	UINT newPixel, closePixel;
348 	int new, refCount;
349 	Tcl_HashEntry *entryPtr;
350 	UINT index;
351 
352 	/*
353 	 * Find the nearest existing palette entry.
354 	 */
355 
356 	newPixel = RGB(entry.peRed, entry.peGreen, entry.peBlue);
357 	index = GetNearestPaletteIndex(cmap->palette, newPixel);
358 	GetPaletteEntries(cmap->palette, index, 1, &closeEntry);
359 	closePixel = RGB(closeEntry.peRed, closeEntry.peGreen,
360 		closeEntry.peBlue);
361 
362 	/*
363 	 * If this is not a duplicate, allocate a new entry. Note that we may
364 	 * get values for index that are above the current size of the
365 	 * palette. This happens because we don't shrink the size of the
366 	 * palette object when we deallocate colors so there may be stale
367 	 * values that match in the upper slots. We should ignore those values
368 	 * and just put the new color in as if the colors had not matched.
369 	 */
370 
371 	if ((index >= cmap->size) || (newPixel != closePixel)) {
372 	    if (cmap->size == sizePalette) {
373 		color->red   = closeEntry.peRed * 257;
374 		color->green = closeEntry.peGreen * 257;
375 		color->blue  = closeEntry.peBlue * 257;
376 		entry = closeEntry;
377 		if (index >= cmap->size) {
378 		    OutputDebugString("XAllocColor: Colormap is bigger than we thought");
379 		}
380 	    } else {
381 		cmap->size++;
382 		ResizePalette(cmap->palette, cmap->size);
383 		SetPaletteEntries(cmap->palette, cmap->size - 1, 1, &entry);
384 	    }
385 	}
386 
387 	color->pixel = PALETTERGB(entry.peRed, entry.peGreen, entry.peBlue);
388 	entryPtr = Tcl_CreateHashEntry(&cmap->refCounts,
389 		INT2PTR(color->pixel), &new);
390 	if (new) {
391 	    refCount = 1;
392 	} else {
393 	    refCount = (PTR2INT(Tcl_GetHashValue(entryPtr))) + 1;
394 	}
395 	Tcl_SetHashValue(entryPtr, INT2PTR(refCount));
396     } else {
397 	/*
398 	 * Determine what color will actually be used on non-colormap systems.
399 	 */
400 
401 	color->pixel = GetNearestColor(dc,
402 		RGB(entry.peRed, entry.peGreen, entry.peBlue));
403 	color->red    = GetRValue(color->pixel) * 257;
404 	color->green  = GetGValue(color->pixel) * 257;
405 	color->blue   = GetBValue(color->pixel) * 257;
406     }
407 
408     ReleaseDC(NULL, dc);
409     return 1;
410 }
411 
412 /*
413  *----------------------------------------------------------------------
414  *
415  * XFreeColors --
416  *
417  *	Deallocate a block of colors.
418  *
419  * Results:
420  *	None.
421  *
422  * Side effects:
423  *	Removes entries for the current palette and compacts the remaining
424  *	set.
425  *
426  *----------------------------------------------------------------------
427  */
428 
429 int
XFreeColors(Display * display,Colormap colormap,unsigned long * pixels,int npixels,unsigned long planes)430 XFreeColors(
431     Display *display,
432     Colormap colormap,
433     unsigned long *pixels,
434     int npixels,
435     unsigned long planes)
436 {
437     TkWinColormap *cmap = (TkWinColormap *) colormap;
438     COLORREF cref;
439     UINT count, index, refCount;
440     int i;
441     PALETTEENTRY entry, *entries;
442     Tcl_HashEntry *entryPtr;
443     HDC dc = GetDC(NULL);
444 
445     /*
446      * We don't have to do anything for non-palette devices.
447      */
448 
449     if (GetDeviceCaps(dc, RASTERCAPS) & RC_PALETTE) {
450 	/*
451 	 * This is really slow for large values of npixels.
452 	 */
453 
454 	for (i = 0; i < npixels; i++) {
455 	    entryPtr = Tcl_FindHashEntry(&cmap->refCounts, INT2PTR(pixels[i]));
456 	    if (!entryPtr) {
457 		Tcl_Panic("Tried to free a color that isn't allocated.");
458 	    }
459 	    refCount = PTR2INT(Tcl_GetHashValue(entryPtr)) - 1;
460 	    if (refCount == 0) {
461 		cref = pixels[i] & 0x00ffffff;
462 		index = GetNearestPaletteIndex(cmap->palette, cref);
463 		GetPaletteEntries(cmap->palette, index, 1, &entry);
464 		if (cref == RGB(entry.peRed, entry.peGreen, entry.peBlue)) {
465 		    count = cmap->size - index;
466 		    entries = (PALETTEENTRY *)
467 			    ckalloc(sizeof(PALETTEENTRY) * count);
468 		    GetPaletteEntries(cmap->palette, index+1, count, entries);
469 		    SetPaletteEntries(cmap->palette, index, count, entries);
470 		    ckfree((char *) entries);
471 		    cmap->size--;
472 		} else {
473 		    Tcl_Panic("Tried to free a color that isn't allocated.");
474 		}
475 		Tcl_DeleteHashEntry(entryPtr);
476 	    } else {
477 		Tcl_SetHashValue(entryPtr, INT2PTR(refCount));
478 	    }
479 	}
480     }
481     ReleaseDC(NULL, dc);
482     return Success;
483 }
484 
485 /*
486  *----------------------------------------------------------------------
487  *
488  * XCreateColormap --
489  *
490  *	Allocate a new colormap.
491  *
492  * Results:
493  *	Returns a newly allocated colormap.
494  *
495  * Side effects:
496  *	Allocates an empty palette and color list.
497  *
498  *----------------------------------------------------------------------
499  */
500 
501 Colormap
XCreateColormap(Display * display,Window w,Visual * visual,int alloc)502 XCreateColormap(
503     Display *display,
504     Window w,
505     Visual *visual,
506     int alloc)
507 {
508     char logPalBuf[sizeof(LOGPALETTE) + 256 * sizeof(PALETTEENTRY)];
509     LOGPALETTE *logPalettePtr;
510     PALETTEENTRY *entryPtr;
511     TkWinColormap *cmap;
512     Tcl_HashEntry *hashPtr;
513     int new;
514     UINT i;
515     HPALETTE sysPal;
516 
517     /*
518      * Allocate a starting palette with all of the reserved colors.
519      */
520 
521     logPalettePtr = (LOGPALETTE *) logPalBuf;
522     logPalettePtr->palVersion = 0x300;
523     sysPal = (HPALETTE) GetStockObject(DEFAULT_PALETTE);
524     logPalettePtr->palNumEntries = GetPaletteEntries(sysPal, 0, 256,
525 	    logPalettePtr->palPalEntry);
526 
527     cmap = (TkWinColormap *) ckalloc(sizeof(TkWinColormap));
528     cmap->size = logPalettePtr->palNumEntries;
529     cmap->stale = 0;
530     cmap->palette = CreatePalette(logPalettePtr);
531 
532     /*
533      * Add hash entries for each of the static colors.
534      */
535 
536     Tcl_InitHashTable(&cmap->refCounts, TCL_ONE_WORD_KEYS);
537     for (i = 0; i < logPalettePtr->palNumEntries; i++) {
538 	entryPtr = logPalettePtr->palPalEntry + i;
539 	hashPtr = Tcl_CreateHashEntry(&cmap->refCounts, INT2PTR(PALETTERGB(
540 		entryPtr->peRed, entryPtr->peGreen, entryPtr->peBlue)), &new);
541 	Tcl_SetHashValue(hashPtr, INT2PTR(1));
542     }
543 
544     return (Colormap)cmap;
545 }
546 
547 /*
548  *----------------------------------------------------------------------
549  *
550  * XFreeColormap --
551  *
552  *	Frees the resources associated with the given colormap.
553  *
554  * Results:
555  *	None.
556  *
557  * Side effects:
558  *	Deletes the palette associated with the colormap. Note that the
559  *	palette must not be selected into a device context when this occurs.
560  *
561  *----------------------------------------------------------------------
562  */
563 
564 int
XFreeColormap(Display * display,Colormap colormap)565 XFreeColormap(
566     Display *display,
567     Colormap colormap)
568 {
569     TkWinColormap *cmap = (TkWinColormap *) colormap;
570 
571     if (!DeleteObject(cmap->palette)) {
572 	Tcl_Panic("Unable to free colormap, palette is still selected.");
573     }
574     Tcl_DeleteHashTable(&cmap->refCounts);
575     ckfree((char *) cmap);
576     return Success;
577 }
578 
579 /*
580  *----------------------------------------------------------------------
581  *
582  * TkWinSelectPalette --
583  *
584  *	This function sets up the specified device context with a given
585  *	palette. If the palette is stale, it realizes it in the background
586  *	unless the palette is the current global palette.
587  *
588  * Results:
589  *	Returns the previous palette selected into the device context.
590  *
591  * Side effects:
592  *	May change the system palette.
593  *
594  *----------------------------------------------------------------------
595  */
596 
597 HPALETTE
TkWinSelectPalette(HDC dc,Colormap colormap)598 TkWinSelectPalette(
599     HDC dc,
600     Colormap colormap)
601 {
602     TkWinColormap *cmap = (TkWinColormap *) colormap;
603     HPALETTE oldPalette;
604 
605     oldPalette = SelectPalette(dc, cmap->palette,
606 	    (cmap->palette == TkWinGetSystemPalette()) ? FALSE : TRUE);
607     RealizePalette(dc);
608     return oldPalette;
609 }
610 
611 /*
612  * Local Variables:
613  * mode: c
614  * c-basic-offset: 4
615  * fill-column: 78
616  * End:
617  */
618