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