1 /* NetHack 3.7 coloratt.c $NHDT-Date: 1737286550 2025/01/19 03:35:50 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.14 $ */
2 /* Copyright (c) Pasi Kallinen, 2024 */
3 /* NetHack may be freely redistributed. See license for details. */
12 static const struct color_names colornames
[] = {
13 { "black", CLR_BLACK
},
15 { "green", CLR_GREEN
},
16 { "brown", CLR_BROWN
},
18 { "magenta", CLR_MAGENTA
},
21 { "orange", CLR_ORANGE
},
22 { "light green", CLR_BRIGHT_GREEN
},
23 { "yellow", CLR_YELLOW
},
24 { "light blue", CLR_BRIGHT_BLUE
},
25 { "light magenta", CLR_BRIGHT_MAGENTA
},
26 { "light cyan", CLR_BRIGHT_CYAN
},
27 { "white", CLR_WHITE
},
28 { "no color", NO_COLOR
},
29 { (const char *) 0, CLR_BLACK
}, /* everything after this is an alias */
30 { "transparent", NO_COLOR
},
31 { "purple", CLR_MAGENTA
},
32 { "light purple", CLR_BRIGHT_MAGENTA
},
33 { "bright purple", CLR_BRIGHT_MAGENTA
},
35 { "bright red", CLR_ORANGE
},
36 { "bright green", CLR_BRIGHT_GREEN
},
37 { "bright blue", CLR_BRIGHT_BLUE
},
38 { "bright magenta", CLR_BRIGHT_MAGENTA
},
39 { "bright cyan", CLR_BRIGHT_CYAN
}
47 static const struct attr_names attrnames
[] = {
51 { "italic", ATR_ITALIC
},
52 { "underline", ATR_ULINE
},
53 { "blink", ATR_BLINK
},
54 { "inverse", ATR_INVERSE
},
55 { (const char *) 0, ATR_NONE
}, /* everything after this is an alias */
56 { "normal", ATR_NONE
},
57 { "uline", ATR_ULINE
},
58 { "reverse", ATR_INVERSE
},
61 /* { colortyp, tableindex, rgbindx, name, hexval, r, g, b }, */
63 static struct nethack_color colortable
[] = {
64 { nh_color
, 0, 0, "black", "", 0, 0, 0 },
65 { nh_color
, 1, 0, "red", "", 255, 0, 0 },
66 { nh_color
, 2, 0, "green", "", 34, 139, 34 },
67 { nh_color
, 3, 0, "brown", "", 165, 42, 42 },
68 { nh_color
, 4, 0, "blue", "", 0, 0, 255 },
69 { nh_color
, 5, 0, "magenta", "", 255, 0, 255 },
70 { nh_color
, 6, 0, "cyan", "", 0, 255, 255 },
71 { nh_color
, 7, 0, "gray", "", 128, 128, 128 },
72 { no_color
, 8, 0, "nocolor", "", 0, 0, 0 },
73 { nh_color
, 9, 0, "orange", "", 255, 165, 0 },
74 { nh_color
, 10, 0, "bright-green", "", 0, 128, 0 },
75 { nh_color
, 11, 0, "yellow", "", 255, 255, 0 },
76 { nh_color
, 12, 0, "bright-blue", "", 173, 216, 230 },
77 { nh_color
, 13, 0, "bright-magenta", "", 147, 112, 219 },
78 { nh_color
, 14, 0, "light-cyan", "", 224, 255, 255 },
79 { nh_color
, 15, 0, "white", "", 255, 255, 255 },
80 { rgb_color
, 16, 0, "maroon", "#800000", 128, 0, 0 },
81 { rgb_color
, 17, 1, "dark-red", "#8B0000", 139, 0, 0 },
82 { rgb_color
, 18, 2, "brown", "#A52A2A", 165, 42, 42 },
83 { rgb_color
, 19, 3, "firebrick", "#B22222", 178, 34, 34 },
84 { rgb_color
, 20, 4, "crimson", "#DC143C", 220, 20, 60 },
85 { rgb_color
, 21, 5, "red", "#FF0000", 255, 0, 0 },
86 { rgb_color
, 22, 6, "tomato", "#FF6347", 255, 99, 71 },
87 { rgb_color
, 23, 7, "coral", "#FF7F50", 255, 127, 80 },
88 { rgb_color
, 24, 8, "indian-red", "#CD5C5C", 205, 92, 92 },
89 { rgb_color
, 25, 9, "light-coral", "#F08080", 240, 128, 128 },
90 { rgb_color
, 26, 10, "dark-salmon", "#E9967A", 233, 150, 122 },
91 { rgb_color
, 27, 11, "salmon", "#FA8072", 250, 128, 114 },
92 { rgb_color
, 28, 12, "light-salmon", "#FFA07A", 255, 160, 122 },
93 { rgb_color
, 29, 13, "orange-red", "#FF4500", 255, 69, 0 },
94 { rgb_color
, 30, 14, "dark-orange", "#FF8C00", 255, 140, 0 },
95 { rgb_color
, 31, 15, "orange", "#FFA500", 255, 165, 0 },
96 { rgb_color
, 32, 16, "gold", "#FFD700", 255, 215, 0 },
97 { rgb_color
, 33, 17, "dark-golden-rod", "#B8860B", 184, 134, 11 },
98 { rgb_color
, 34, 18, "golden-rod", "#DAA520", 218, 165, 32 },
99 { rgb_color
, 35, 19, "pale-golden-rod", "#EEE8AA", 238, 232, 170 },
100 { rgb_color
, 36, 20, "dark-khaki", "#BDB76B", 189, 183, 107 },
101 { rgb_color
, 37, 21, "khaki", "#F0E68C", 240, 230, 140 },
102 { rgb_color
, 38, 22, "olive", "#808000", 128, 128, 0 },
103 { rgb_color
, 39, 23, "yellow", "#FFFF00", 255, 255, 0 },
104 { rgb_color
, 40, 24, "yellow-green", "#9ACD32", 154, 205, 50 },
105 { rgb_color
, 41, 25, "dark-olive-green", "#556B2F", 85, 107, 47 },
106 { rgb_color
, 42, 26, "olive-drab", "#6B8E23", 107, 142, 35 },
107 { rgb_color
, 43, 27, "lawn-green", "#7CFC00", 124, 252, 0 },
108 { rgb_color
, 44, 28, "chart-reuse", "#7FFF00", 127, 255, 0 },
109 { rgb_color
, 45, 29, "green-yellow", "#ADFF2F", 173, 255, 47 },
110 { rgb_color
, 46, 30, "dark-green", "#006400", 0, 100, 0 },
111 { rgb_color
, 47, 31, "green", "#008000", 0, 128, 0 },
112 { rgb_color
, 48, 32, "forest-green", "#228B22", 34, 139, 34 },
113 { rgb_color
, 49, 33, "lime", "#00FF00", 0, 255, 0 },
114 { rgb_color
, 50, 34, "lime-green", "#32CD32", 50, 205, 50 },
115 { rgb_color
, 51, 35, "light-green", "#90EE90", 144, 238, 144 },
116 { rgb_color
, 52, 36, "pale-green", "#98FB98", 152, 251, 152 },
117 { rgb_color
, 53, 37, "dark-sea-green", "#8FBC8F", 143, 188, 143 },
118 { rgb_color
, 54, 38, "medium-spring-green", "#00FA9A", 0, 250, 154 },
119 { rgb_color
, 55, 39, "spring-green", "#00FF7F", 0, 255, 127 },
120 { rgb_color
, 56, 40, "sea-green", "#2E8B57", 46, 139, 87 },
121 { rgb_color
, 57, 41, "medium-aqua-marine", "#66CDAA", 102, 205, 170 },
122 { rgb_color
, 58, 42, "medium-sea-green", "#3CB371", 60, 179, 113 },
123 { rgb_color
, 59, 43, "light-sea-green", "#20B2AA", 32, 178, 170 },
124 { rgb_color
, 60, 44, "dark-slate-gray", "#2F4F4F", 47, 79, 79 },
125 { rgb_color
, 61, 45, "teal", "#008080", 0, 128, 128 },
126 { rgb_color
, 62, 46, "dark-cyan", "#008B8B", 0, 139, 139 },
127 { rgb_color
, 63, 47, "aqua", "#00FFFF", 0, 255, 255 },
128 { rgb_color
, 64, 48, "cyan", "#00FFFF", 0, 255, 255 },
129 { rgb_color
, 65, 49, "light-cyan", "#E0FFFF", 224, 255, 255 },
130 { rgb_color
, 66, 50, "dark-turquoise", "#00CED1", 0, 206, 209 },
131 { rgb_color
, 67, 51, "turquoise", "#40E0D0", 64, 224, 208 },
132 { rgb_color
, 68, 52, "medium-turquoise", "#48D1CC", 72, 209, 204 },
133 { rgb_color
, 69, 53, "pale-turquoise", "#AFEEEE", 175, 238, 238 },
134 { rgb_color
, 70, 54, "aqua-marine", "#7FFFD4", 127, 255, 212 },
135 { rgb_color
, 71, 55, "powder-blue", "#B0E0E6", 176, 224, 230 },
136 { rgb_color
, 72, 56, "cadet-blue", "#5F9EA0", 95, 158, 160 },
137 { rgb_color
, 73, 57, "steel-blue", "#4682B4", 70, 130, 180 },
138 { rgb_color
, 74, 58, "corn-flower-blue", "#6495ED", 100, 149, 237 },
139 { rgb_color
, 75, 59, "deep-sky-blue", "#00BFFF", 0, 191, 255 },
140 { rgb_color
, 76, 60, "dodger-blue", "#1E90FF", 30, 144, 255 },
141 { rgb_color
, 77, 61, "light-blue", "#ADD8E6", 173, 216, 230 },
142 { rgb_color
, 78, 62, "sky-blue", "#87CEEB", 135, 206, 235 },
143 { rgb_color
, 79, 63, "light-sky-blue", "#87CEFA", 135, 206, 250 },
144 { rgb_color
, 80, 64, "midnight-blue", "#191970", 25, 25, 112 },
145 { rgb_color
, 81, 65, "navy", "#000080", 0, 0, 128 },
146 { rgb_color
, 82, 66, "dark-blue", "#00008B", 0, 0, 139 },
147 { rgb_color
, 83, 67, "medium-blue", "#0000CD", 0, 0, 205 },
148 { rgb_color
, 84, 68, "blue", "#0000FF", 0, 0, 255 },
149 { rgb_color
, 85, 69, "royal-blue", "#4169E1", 65, 105, 225 },
150 { rgb_color
, 86, 70, "blue-violet", "#8A2BE2", 138, 43, 226 },
151 { rgb_color
, 87, 71, "indigo", "#4B0082", 75, 0, 130 },
152 { rgb_color
, 88, 72, "dark-slate-blue", "#483D8B", 72, 61, 139 },
153 { rgb_color
, 89, 73, "slate-blue", "#6A5ACD", 106, 90, 205 },
154 { rgb_color
, 90, 74, "medium-slate-blue", "#7B68EE", 123, 104, 238 },
155 { rgb_color
, 91, 75, "medium-purple", "#9370DB", 147, 112, 219 },
156 { rgb_color
, 92, 76, "dark-magenta", "#8B008B", 139, 0, 139 },
157 { rgb_color
, 93, 77, "dark-violet", "#9400D3", 148, 0, 211 },
158 { rgb_color
, 94, 78, "dark-orchid", "#9932CC", 153, 50, 204 },
159 { rgb_color
, 95, 79, "medium-orchid", "#BA55D3", 186, 85, 211 },
160 { rgb_color
, 96, 80, "purple", "#800080", 128, 0, 128 },
161 { rgb_color
, 97, 81, "thistle", "#D8BFD8", 216, 191, 216 },
162 { rgb_color
, 98, 82, "plum", "#DDA0DD", 221, 160, 221 },
163 { rgb_color
, 99, 83, "violet", "#EE82EE", 238, 130, 238 },
164 { rgb_color
, 100, 84, "magenta", "#FF00FF", 255, 0, 255 },
165 { rgb_color
, 101, 85, "orchid", "#DA70D6", 218, 112, 214 },
166 { rgb_color
, 102, 86, "medium-violet-red", "#C71585", 199, 21, 133 },
167 { rgb_color
, 103, 87, "pale-violet-red", "#DB7093", 219, 112, 147 },
168 { rgb_color
, 104, 88, "deep-pink", "#FF1493", 255, 20, 147 },
169 { rgb_color
, 105, 89, "hot-pink", "#FF69B4", 255, 105, 180 },
170 { rgb_color
, 106, 90, "light-pink", "#FFB6C1", 255, 182, 193 },
171 { rgb_color
, 107, 91, "pink", "#FFC0CB", 255, 192, 203 },
172 { rgb_color
, 108, 92, "antique-white", "#FAEBD7", 250, 235, 215 },
173 { rgb_color
, 109, 93, "beige", "#F5F5DC", 245, 245, 220 },
174 { rgb_color
, 110, 94, "bisque", "#FFE4C4", 255, 228, 196 },
175 { rgb_color
, 111, 95, "blanched-almond", "#FFEBCD", 255, 235, 205 },
176 { rgb_color
, 112, 96, "wheat", "#F5DEB3", 245, 222, 179 },
177 { rgb_color
, 113, 97, "corn-silk", "#FFF8DC", 255, 248, 220 },
178 { rgb_color
, 114, 98, "lemon-chiffon", "#FFFACD", 255, 250, 205 },
179 { rgb_color
, 115, 99, "light-golden-rod-yellow", "#FAFAD2", 250, 250, 210 },
180 { rgb_color
, 116, 100, "light-yellow", "#FFFFE0", 255, 255, 224 },
181 { rgb_color
, 117, 101, "saddle-brown", "#8B4513", 139, 69, 19 },
182 { rgb_color
, 118, 102, "sienna", "#A0522D", 160, 82, 45 },
183 { rgb_color
, 119, 103, "chocolate", "#D2691E", 210, 105, 30 },
184 { rgb_color
, 120, 104, "peru", "#CD853F", 205, 133, 63 },
185 { rgb_color
, 121, 105, "sandy-brown", "#F4A460", 244, 164, 96 },
186 { rgb_color
, 122, 106, "burly-wood", "#DEB887", 222, 184, 135 },
187 { rgb_color
, 123, 107, "tan", "#D2B48C", 210, 180, 140 },
188 { rgb_color
, 124, 108, "rosy-brown", "#BC8F8F", 188, 143, 143 },
189 { rgb_color
, 125, 109, "moccasin", "#FFE4B5", 255, 228, 181 },
190 { rgb_color
, 126, 110, "navajo-white", "#FFDEAD", 255, 222, 173 },
191 { rgb_color
, 127, 111, "peach-puff", "#FFDAB9", 255, 218, 185 },
192 { rgb_color
, 128, 112, "misty-rose", "#FFE4E1", 255, 228, 225 },
193 { rgb_color
, 129, 113, "lavender-blush", "#FFF0F5", 255, 240, 245 },
194 { rgb_color
, 130, 114, "linen", "#FAF0E6", 250, 240, 230 },
195 { rgb_color
, 131, 115, "old-lace", "#FDF5E6", 253, 245, 230 },
196 { rgb_color
, 132, 116, "papaya-whip", "#FFEFD5", 255, 239, 213 },
197 { rgb_color
, 133, 117, "sea-shell", "#FFF5EE", 255, 245, 238 },
198 { rgb_color
, 134, 118, "mint-cream", "#F5FFFA", 245, 255, 250 },
199 { rgb_color
, 135, 119, "slate-gray", "#708090", 112, 128, 144 },
200 { rgb_color
, 136, 120, "light-slate-gray", "#778899", 119, 136, 153 },
201 { rgb_color
, 137, 121, "light-steel-blue", "#B0C4DE", 176, 196, 222 },
202 { rgb_color
, 138, 122, "lavender", "#E6E6FA", 230, 230, 250 },
203 { rgb_color
, 139, 123, "floral-white", "#FFFAF0", 255, 250, 240 },
204 { rgb_color
, 140, 124, "alice-blue", "#F0F8FF", 240, 248, 255 },
205 { rgb_color
, 141, 125, "ghost-white", "#F8F8FF", 248, 248, 255 },
206 { rgb_color
, 142, 126, "honeydew", "#F0FFF0", 240, 255, 240 },
207 { rgb_color
, 143, 127, "ivory", "#FFFFF0", 255, 255, 240 },
208 { rgb_color
, 144, 128, "azure", "#F0FFFF", 240, 255, 255 },
209 { rgb_color
, 145, 129, "snow", "#FFFAFA", 255, 250, 250 },
210 { rgb_color
, 146, 130, "black", "#000000", 0, 0, 0 },
211 { rgb_color
, 147, 131, "dim-gray", "#696969", 105, 105, 105 },
212 { rgb_color
, 148, 132, "gray", "#808080", 128, 128, 128 },
213 { rgb_color
, 149, 133, "dark-gray", "#A9A9A9", 169, 169, 169 },
214 { rgb_color
, 150, 134, "silver", "#C0C0C0", 192, 192, 192 },
215 { rgb_color
, 151, 135, "light-gray", "#D3D3D3", 211, 211, 211 },
216 { rgb_color
, 152, 136, "gainsboro", "#DCDCDC", 220, 220, 220 },
217 { rgb_color
, 153, 137, "white-smoke", "#F5F5F5", 245, 245, 245 },
218 { rgb_color
, 154, 138, "white", "#FFFFFF", 255, 255, 255 },
222 staticfn int32
alt_color_spec(const char *cp
);
226 colortable_to_int32(struct nethack_color
*cte
)
228 int32 clr
= NO_COLOR
| NH_BASIC_COLOR
;
230 if (cte
->colortyp
== rgb_color
)
231 clr
= (cte
->r
<< 16) | (cte
->g
<< 8) | cte
->b
;
232 else if (cte
->colortyp
== nh_color
)
233 clr
= cte
->tableindex
| NH_BASIC_COLOR
;
238 color_attr_to_str(color_attr
*ca
)
240 static char buf
[BUFSZ
];
242 Sprintf(buf
, "%s&%s",
243 clr2colorname(ca
->color
),
244 attr2attrname(ca
->attr
));
248 /* parse string like "color&attr" into color_attr */
250 color_attr_parse_str(color_attr
*ca
, char *str
)
254 int tmp
, c
= NO_COLOR
, a
= ATR_NONE
;
256 (void) strncpy(buf
, str
, sizeof buf
- 1);
257 buf
[sizeof buf
- 1] = '\0';
259 if ((amp
= strchr(buf
, '&')) != 0)
264 c
= match_str2clr(buf
, FALSE
);
265 a
= match_str2attr(amp
, TRUE
);
266 /* FIXME: match_str2clr & match_str2attr give config_error_add(),
267 so this is useless */
268 if (c
>= CLR_MAX
&& a
== -1) {
269 /* try other way around */
270 c
= match_str2clr(amp
, FALSE
);
271 a
= match_str2attr(buf
, TRUE
);
273 if (c
>= CLR_MAX
|| a
== -1)
277 tmp
= match_str2attr(buf
, FALSE
);
279 tmp
= match_str2clr(buf
, FALSE
);
293 query_color_attr(color_attr
*ca
, const char *prompt
)
297 c
= query_color(prompt
, ca
->color
);
300 a
= query_attr(prompt
, ca
->attr
);
309 attr2attrname(int attr
)
313 for (i
= 0; i
< SIZE(attrnames
); i
++)
314 if (attrnames
[i
].attr
== attr
)
315 return attrnames
[i
].name
;
320 * Color support functions and data for "color"
327 clr2colorname(int clr
)
331 for (i
= 0; i
< SIZE(colornames
); i
++)
332 if (colornames
[i
].name
&& colornames
[i
].color
== clr
)
333 return colornames
[i
].name
;
338 match_str2clr(char *str
, boolean suppress_msg
)
342 /* allow "lightblue", "light blue", and "light-blue" to match "light blue"
343 (also junk like "_l i-gh_t---b l u e" but we won't worry about that);
344 also copes with trailing space; caller has removed any leading space */
345 for (i
= 0; i
< SIZE(colornames
); i
++)
346 if (colornames
[i
].name
347 && fuzzymatch(str
, colornames
[i
].name
, " -_", TRUE
)) {
348 c
= colornames
[i
].color
;
351 if (i
== SIZE(colornames
) && digit(*str
))
354 if (c
< 0 || c
>= CLR_MAX
) {
356 config_error_add("Unknown color '%.60s'", str
);
357 c
= CLR_MAX
; /* "none of the above" */
363 match_str2attr(const char *str
, boolean complain
)
367 for (i
= 0; i
< SIZE(attrnames
); i
++)
368 if (attrnames
[i
].name
369 && fuzzymatch(str
, attrnames
[i
].name
, " -_", TRUE
)) {
370 a
= attrnames
[i
].attr
;
374 if (a
== -1 && complain
)
375 config_error_add("Unknown text attribute '%.50s'", str
);
380 /* ask about highlighting attribute; for menu headers and menu
381 coloring patterns, only one attribute at a time is allowed;
382 for status highlighting, multiple attributes are allowed [overkill;
383 life would be much simpler if that were restricted to one also...] */
385 query_attr(const char *prompt
, int dflt_attr
)
390 menu_item
*picks
= (menu_item
*) 0;
391 boolean allow_many
= (prompt
&& !strncmpi(prompt
, "Choose", 6));
394 tmpwin
= create_nhwindow(NHW_MENU
);
395 start_menu(tmpwin
, MENU_BEHAVE_STANDARD
);
397 for (i
= 0; i
< SIZE(attrnames
); i
++) {
398 if (!attrnames
[i
].name
)
401 add_menu(tmpwin
, &nul_glyphinfo
, &any
, 0, 0,
402 attrnames
[i
].attr
, clr
, attrnames
[i
].name
,
403 (attrnames
[i
].attr
== dflt_attr
) ? MENU_ITEMFLAGS_SELECTED
404 : MENU_ITEMFLAGS_NONE
);
406 end_menu(tmpwin
, (prompt
&& *prompt
) ? prompt
: "Pick an attribute");
407 pick_cnt
= select_menu(tmpwin
, allow_many
? PICK_ANY
: PICK_ONE
, &picks
);
408 destroy_nhwindow(tmpwin
);
413 /* PICK_ANY, with one preselected entry (ATR_NONE) which
414 should be excluded if any other choices were picked */
415 for (i
= 0; i
< pick_cnt
; ++i
) {
416 j
= picks
[i
].item
.a_int
- 1;
417 if (attrnames
[j
].attr
!= ATR_NONE
|| pick_cnt
== 1) {
418 switch (attrnames
[j
].attr
) {
444 /* PICK_ONE, but might get 0 or 2 due to preselected entry */
445 j
= picks
[0].item
.a_int
- 1;
446 /* pick_cnt==2: explicitly picked something other than the
448 if (pick_cnt
== 2 && attrnames
[j
].attr
== dflt_attr
)
449 j
= picks
[1].item
.a_int
- 1;
450 k
= attrnames
[j
].attr
;
452 free((genericptr_t
) picks
);
454 } else if (pick_cnt
== 0 && !allow_many
) {
455 /* PICK_ONE, preselected entry explicitly chosen */
458 /* either ESC to explicitly cancel (pick_cnt==-1) or
459 PICK_ANY with preselected entry toggled off and nothing chosen */
464 query_color(const char *prompt
, int dflt_color
)
469 menu_item
*picks
= (menu_item
*) 0;
471 /* replace user patterns with color name ones and force 'menucolors' On */
472 basic_menu_colors(TRUE
);
474 tmpwin
= create_nhwindow(NHW_MENU
);
475 start_menu(tmpwin
, MENU_BEHAVE_STANDARD
);
477 for (i
= 0; i
< SIZE(colornames
); i
++) {
478 if (!colornames
[i
].name
)
481 add_menu(tmpwin
, &nul_glyphinfo
, &any
, 0, 0,
482 ATR_NONE
, NO_COLOR
, colornames
[i
].name
,
483 (colornames
[i
].color
== dflt_color
) ? MENU_ITEMFLAGS_SELECTED
484 : MENU_ITEMFLAGS_NONE
);
486 end_menu(tmpwin
, (prompt
&& *prompt
) ? prompt
: "Pick a color");
487 pick_cnt
= select_menu(tmpwin
, PICK_ONE
, &picks
);
488 destroy_nhwindow(tmpwin
);
490 /* remove temporary color name patterns and restore user-specified ones;
491 reset 'menucolors' option to its previous value */
492 basic_menu_colors(FALSE
);
495 i
= colornames
[picks
[0].item
.a_int
- 1].color
;
496 /* pick_cnt==2: explicitly picked something other than the
498 if (pick_cnt
== 2 && i
== NO_COLOR
)
499 i
= colornames
[picks
[1].item
.a_int
- 1].color
;
500 free((genericptr_t
) picks
);
502 } else if (pick_cnt
== 0) {
503 /* pick_cnt==0: explicitly picking preselected entry toggled it off */
509 DISABLE_WARNING_FORMAT_NONLITERAL
511 extern const char regex_id
[]; /* from sys/share/<various>regex.{c,cpp} */
513 /* set up a menu for picking a color, one that shows each name in its color;
514 overrides player's MENUCOLORS with a set of "blue"=blue, "red"=red, and
515 so forth; suppresses color for black and white because one of those will
516 likely be invisible due to matching the background; the alternate set of
517 MENUCOLORS is kept around for potential re-use */
520 boolean load_colors
) /* True: temporarily replace menu color entries with
521 * a fake set of menu colors which match their names;
522 * False: restore user-specified colorings */
525 /* replace normal menu colors with a set specifically for colors */
526 gs
.save_menucolors
= iflags
.use_menu_color
;
527 gs
.save_colorings
= gm
.menu_colorings
;
529 iflags
.use_menu_color
= TRUE
;
530 if (gc
.color_colorings
) {
531 /* use the alternate colorings which were set up previously */
532 gm
.menu_colorings
= gc
.color_colorings
;
534 /* create the alternate colorings once */
537 boolean pmatchregex
= !strcmpi(regex_id
, "pmatchregex");
538 const char *patternfmt
= pmatchregex
? "*%s" : "%s";
540 /* menu_colorings pointer has been saved; clear it in order
541 to add the alternate entries as if from scratch */
542 gm
.menu_colorings
= (struct menucoloring
*) 0;
544 /* this orders the patterns last-in/first-out; that means
545 that the "light <foo>" variations come before the basic
546 "<foo>" ones, which is exactly what we want (so that the
547 shorter basic names won't get false matches as substrings
548 of the longer ones) */
549 for (i
= 0; i
< SIZE(colornames
); ++i
) {
550 if (!colornames
[i
].name
) /* first alias entry has no name */
552 c
= colornames
[i
].color
;
553 if (c
== CLR_BLACK
|| c
== CLR_WHITE
|| c
== NO_COLOR
)
554 continue; /* skip these */
555 Sprintf(cnm
, patternfmt
, colornames
[i
].name
);
556 add_menu_coloring_parsed(cnm
, c
, ATR_NONE
);
559 /* right now, menu_colorings contains the alternate color list;
560 remember that list for future pick-a-color instances and
561 also keep it as is for this instance */
562 gc
.color_colorings
= gm
.menu_colorings
;
565 /* restore normal user-specified menu colors */
566 iflags
.use_menu_color
= gs
.save_menucolors
;
567 gm
.menu_colorings
= gs
.save_colorings
;
571 RESTORE_WARNING_FORMAT_NONLITERAL
574 add_menu_coloring_parsed(const char *str
, int c
, int a
)
576 static const char re_error
[] = "Menucolor regex error";
577 struct menucoloring
*tmp
;
581 tmp
= (struct menucoloring
*) alloc(sizeof *tmp
);
582 tmp
->match
= regex_init();
583 /* test_regex_pattern() has already validated this regexp but parsing
584 it again could conceivably run out of memory */
585 if (!regex_compile(str
, tmp
->match
)) {
587 char *re_error_desc
= regex_error_desc(tmp
->match
, errbuf
);
589 /* free first in case reason for regcomp failure was out-of-memory */
590 regex_free(tmp
->match
);
591 free((genericptr_t
) tmp
);
592 config_error_add("%s: %s", re_error
, re_error_desc
);
595 tmp
->next
= gm
.menu_colorings
;
596 tmp
->origstr
= dupstr(str
);
599 gm
.menu_colorings
= tmp
;
600 iflags
.use_menu_color
= TRUE
;
604 /* parse '"regex_string"=color&attr' and add it to menucoloring */
606 add_menu_coloring(char *tmpstr
) /* never Null but could be empty */
608 int c
= NO_COLOR
, a
= ATR_NONE
;
609 char *tmps
, *cs
, *amp
;
612 (void) strncpy(str
, tmpstr
, sizeof str
- 1);
613 str
[sizeof str
- 1] = '\0';
615 if ((cs
= strchr(str
, '=')) == 0) {
616 config_error_add("Malformed MENUCOLOR");
620 tmps
= cs
+ 1; /* advance past '=' */
622 if ((amp
= strchr(tmps
, '&')) != 0)
625 c
= match_str2clr(tmps
, FALSE
);
630 tmps
= amp
+ 1; /* advance past '&' */
631 a
= match_str2attr(tmps
, TRUE
);
636 /* the regexp portion here has not been condensed by mungspaces() */
639 if (*tmps
== '"' || *tmps
== '\'') {
641 while (isspace((uchar
) *cs
))
648 return add_menu_coloring_parsed(tmps
, c
, a
);
651 /* release all menu color patterns */
653 free_menu_coloring(void)
655 /* either menu_colorings or color_colorings or both might need to
656 be freed or already be Null; do-loop will iterate at most twice */
658 struct menucoloring
*tmp
, *tmp2
;
660 for (tmp
= gm
.menu_colorings
; tmp
; tmp
= tmp2
) {
662 regex_free(tmp
->match
);
663 free((genericptr_t
) tmp
->origstr
);
664 free((genericptr_t
) tmp
);
666 gm
.menu_colorings
= gc
.color_colorings
;
667 gc
.color_colorings
= (struct menucoloring
*) 0;
668 } while (gm
.menu_colorings
);
671 /* release a specific menu color pattern; not used for color_colorings */
673 free_one_menu_coloring(int idx
) /* 0 .. */
675 struct menucoloring
*tmp
= gm
.menu_colorings
;
676 struct menucoloring
*prev
= NULL
;
680 struct menucoloring
*next
= tmp
->next
;
682 regex_free(tmp
->match
);
683 free((genericptr_t
) tmp
->origstr
);
684 free((genericptr_t
) tmp
);
688 gm
.menu_colorings
= next
;
698 count_menucolors(void)
700 struct menucoloring
*tmp
;
703 for (tmp
= gm
.menu_colorings
; tmp
; tmp
= tmp
->next
)
708 /* returns -1 on no-match.
712 check_enhanced_colors(char *buf
)
714 char xtra
= '\0'; /* used to catch trailing junk after "#rrggbb" */
716 int32 retcolor
= -1, color
;
718 if ((color
= match_str2clr(buf
, TRUE
)) != CLR_MAX
) {
719 retcolor
= color
| NH_BASIC_COLOR
;
720 } else if (sscanf(buf
, "#%02x%02x%02x%c", &r
, &g
, &b
, &xtra
) >= 3) {
721 retcolor
= !xtra
? (int32
) ((r
<< 16) | (g
<< 8) | b
) : -1;
723 /* altbuf: allow user's "grey" to match colortable[]'s "gray";
724 * fuzzymatch(): ignore spaces, hyphens, and underscores so that
725 * space or underscore in user-supplied name will match hyphen
726 * [note: caller splits text at spaces so we won't see any here]
728 char *altbuf
= NULL
, *grey
= strstri(buf
, "grey");
729 ptrdiff_t greyoffset
= grey
? (grey
- buf
) : -1;
731 if (greyoffset
>= 0) {
732 altbuf
= dupstr(buf
);
733 /* use direct copy because strsubst() is case-sensitive */
734 /*(void) strncpy(&altbuf[greyoffset], "gray", 4);*/
735 (void) memcpy(altbuf
+ greyoffset
, "gray", 4);
737 for (color
= 0; color
< SIZE(colortable
); ++color
) {
738 if (fuzzymatch(buf
, colortable
[color
].name
, " -_", TRUE
)
739 || (altbuf
&& fuzzymatch(altbuf
, colortable
[color
].name
,
741 retcolor
= colortable_to_int32(&colortable
[color
]);
751 /* return the canonical name of a particular color */
753 wc_color_name(int32 colorindx
)
755 static char hexcolor
[sizeof "#rrggbb"]; /* includes room for '\0' */
756 const char *result
= "no-color";
758 if (colorindx
>= 0) {
759 int32 basicindx
= colorindx
& ~NH_BASIC_COLOR
;
761 /* if colorindx has NH_BASIC_COLOR bit set, basicindx won't,
762 so differing implies a basic color */
763 if (basicindx
!= colorindx
) {
764 assert(basicindx
< 16);
765 result
= colortable
[basicindx
].name
;
768 long r
= (colorindx
>> 16) & 0x0000ff, /* shift rrXXXX to rr */
769 g
= (colorindx
>> 8) & 0x0000ff, /* shift XXggXX to gg */
770 b
= colorindx
& 0x0000ff; /* mask XXXXbb to bb */
772 Snprintf(hexcolor
, sizeof hexcolor
, "#%02x%02x%02x",
773 (uint8
) r
, (uint8
) g
, (uint8
) b
);
775 /* override hex value if this is a named color */
776 for (indx
= 16; indx
< SIZE(colortable
); ++indx
)
777 if (colortable
[indx
].r
== r
778 && colortable
[indx
].g
== g
779 && colortable
[indx
].b
== b
) {
780 result
= colortable
[indx
].name
;
788 /* hexdd[] is defined in decl.c */
790 onlyhexdigits(const char *buf
)
792 const char *dp
= buf
;
794 for (dp
= buf
; *dp
; ++dp
) {
795 if (!(strchr(hexdd
, *dp
) || *dp
== '-'))
802 rgbstr_to_int32(const char *rgbstr
)
804 int r
, g
, b
, milestone
= 0;
805 char *cp
, *c_r
, *c_g
, *c_b
;
808 boolean dash
= FALSE
;
811 Snprintf(buf
, sizeof buf
, "%s",
812 rgbstr
? rgbstr
: "");
814 if (*buf
&& onlyhexdigits(buf
)) {
815 c_g
= c_b
= (char *) 0;
818 if (digit(*cp
) || *cp
== '-') {
837 if (c_r
&& c_g
&& c_b
838 && (strlen(c_r
) > 0 && strlen(c_r
) < 4)
839 && (strlen(c_g
) > 0 && strlen(c_g
) < 4)
840 && (strlen(c_b
) > 0 && strlen(c_b
) < 4)) {
844 rgb
= (r
<< 16) | (g
<< 8) | (b
<< 0);
848 /* perhaps an enhanced color name was used instead of rgb value? */
849 if ((rgb
= check_enhanced_colors(buf
)) != -1) {
857 set_map_customcolor(glyph_map
*gmap
, uint32 nhcolor
)
859 glyph_map
*tmpgm
= gmap
;
860 uint32 closecolor
= 0;
866 gmap
->customcolor
= nhcolor
;
867 if (closest_color(nhcolor
, &closecolor
, &clridx
))
868 gmap
->color256idx
= clridx
;
870 gmap
->color256idx
= 0;
877 } color_256_definitions
[] = {
878 /* color values are from unnethack */
879 { 16, 0x000000 }, { 17, 0x00005f }, { 18, 0x000087 },
880 { 19, 0x0000af }, { 20, 0x0000d7 }, { 21, 0x0000ff },
881 { 22, 0x005f00 }, { 23, 0x005f5f }, { 24, 0x005f87 },
882 { 25, 0x005faf }, { 26, 0x005fd7 }, { 27, 0x005fff },
883 { 28, 0x008700 }, { 29, 0x00875f }, { 30, 0x008787 },
884 { 31, 0x0087af }, { 32, 0x0087d7 }, { 33, 0x0087ff },
885 { 34, 0x00af00 }, { 35, 0x00af5f }, { 36, 0x00af87 },
886 { 37, 0x00afaf }, { 38, 0x00afd7 }, { 39, 0x00afff },
887 { 40, 0x00d700 }, { 41, 0x00d75f }, { 42, 0x00d787 },
888 { 43, 0x00d7af }, { 44, 0x00d7d7 }, { 45, 0x00d7ff },
889 { 46, 0x00ff00 }, { 47, 0x00ff5f }, { 48, 0x00ff87 },
890 { 49, 0x00ffaf }, { 50, 0x00ffd7 }, { 51, 0x00ffff },
891 { 52, 0x5f0000 }, { 53, 0x5f005f }, { 54, 0x5f0087 },
892 { 55, 0x5f00af }, { 56, 0x5f00d7 }, { 57, 0x5f00ff },
893 { 58, 0x5f5f00 }, { 59, 0x5f5f5f }, { 60, 0x5f5f87 },
894 { 61, 0x5f5faf }, { 62, 0x5f5fd7 }, { 63, 0x5f5fff },
895 { 64, 0x5f8700 }, { 65, 0x5f875f }, { 66, 0x5f8787 },
896 { 67, 0x5f87af }, { 68, 0x5f87d7 }, { 69, 0x5f87ff },
897 { 70, 0x5faf00 }, { 71, 0x5faf5f }, { 72, 0x5faf87 },
898 { 73, 0x5fafaf }, { 74, 0x5fafd7 }, { 75, 0x5fafff },
899 { 76, 0x5fd700 }, { 77, 0x5fd75f }, { 78, 0x5fd787 },
900 { 79, 0x5fd7af }, { 80, 0x5fd7d7 }, { 81, 0x5fd7ff },
901 { 82, 0x5fff00 }, { 83, 0x5fff5f }, { 84, 0x5fff87 },
902 { 85, 0x5fffaf }, { 86, 0x5fffd7 }, { 87, 0x5fffff },
903 { 88, 0x870000 }, { 89, 0x87005f }, { 90, 0x870087 },
904 { 91, 0x8700af }, { 92, 0x8700d7 }, { 93, 0x8700ff },
905 { 94, 0x875f00 }, { 95, 0x875f5f }, { 96, 0x875f87 },
906 { 97, 0x875faf }, { 98, 0x875fd7 }, { 99, 0x875fff },
907 { 100, 0x878700 }, { 101, 0x87875f }, { 102, 0x878787 },
908 { 103, 0x8787af }, { 104, 0x8787d7 }, { 105, 0x8787ff },
909 { 106, 0x87af00 }, { 107, 0x87af5f }, { 108, 0x87af87 },
910 { 109, 0x87afaf }, { 110, 0x87afd7 }, { 111, 0x87afff },
911 { 112, 0x87d700 }, { 113, 0x87d75f }, { 114, 0x87d787 },
912 { 115, 0x87d7af }, { 116, 0x87d7d7 }, { 117, 0x87d7ff },
913 { 118, 0x87ff00 }, { 119, 0x87ff5f }, { 120, 0x87ff87 },
914 { 121, 0x87ffaf }, { 122, 0x87ffd7 }, { 123, 0x87ffff },
915 { 124, 0xaf0000 }, { 125, 0xaf005f }, { 126, 0xaf0087 },
916 { 127, 0xaf00af }, { 128, 0xaf00d7 }, { 129, 0xaf00ff },
917 { 130, 0xaf5f00 }, { 131, 0xaf5f5f }, { 132, 0xaf5f87 },
918 { 133, 0xaf5faf }, { 134, 0xaf5fd7 }, { 135, 0xaf5fff },
919 { 136, 0xaf8700 }, { 137, 0xaf875f }, { 138, 0xaf8787 },
920 { 139, 0xaf87af }, { 140, 0xaf87d7 }, { 141, 0xaf87ff },
921 { 142, 0xafaf00 }, { 143, 0xafaf5f }, { 144, 0xafaf87 },
922 { 145, 0xafafaf }, { 146, 0xafafd7 }, { 147, 0xafafff },
923 { 148, 0xafd700 }, { 149, 0xafd75f }, { 150, 0xafd787 },
924 { 151, 0xafd7af }, { 152, 0xafd7d7 }, { 153, 0xafd7ff },
925 { 154, 0xafff00 }, { 155, 0xafff5f }, { 156, 0xafff87 },
926 { 157, 0xafffaf }, { 158, 0xafffd7 }, { 159, 0xafffff },
927 { 160, 0xd70000 }, { 161, 0xd7005f }, { 162, 0xd70087 },
928 { 163, 0xd700af }, { 164, 0xd700d7 }, { 165, 0xd700ff },
929 { 166, 0xd75f00 }, { 167, 0xd75f5f }, { 168, 0xd75f87 },
930 { 169, 0xd75faf }, { 170, 0xd75fd7 }, { 171, 0xd75fff },
931 { 172, 0xd78700 }, { 173, 0xd7875f }, { 174, 0xd78787 },
932 { 175, 0xd787af }, { 176, 0xd787d7 }, { 177, 0xd787ff },
933 { 178, 0xd7af00 }, { 179, 0xd7af5f }, { 180, 0xd7af87 },
934 { 181, 0xd7afaf }, { 182, 0xd7afd7 }, { 183, 0xd7afff },
935 { 184, 0xd7d700 }, { 185, 0xd7d75f }, { 186, 0xd7d787 },
936 { 187, 0xd7d7af }, { 188, 0xd7d7d7 }, { 189, 0xd7d7ff },
937 { 190, 0xd7ff00 }, { 191, 0xd7ff5f }, { 192, 0xd7ff87 },
938 { 193, 0xd7ffaf }, { 194, 0xd7ffd7 }, { 195, 0xd7ffff },
939 { 196, 0xff0000 }, { 197, 0xff005f }, { 198, 0xff0087 },
940 { 199, 0xff00af }, { 200, 0xff00d7 }, { 201, 0xff00ff },
941 { 202, 0xff5f00 }, { 203, 0xff5f5f }, { 204, 0xff5f87 },
942 { 205, 0xff5faf }, { 206, 0xff5fd7 }, { 207, 0xff5fff },
943 { 208, 0xff8700 }, { 209, 0xff875f }, { 210, 0xff8787 },
944 { 211, 0xff87af }, { 212, 0xff87d7 }, { 213, 0xff87ff },
945 { 214, 0xffaf00 }, { 215, 0xffaf5f }, { 216, 0xffaf87 },
946 { 217, 0xffafaf }, { 218, 0xffafd7 }, { 219, 0xffafff },
947 { 220, 0xffd700 }, { 221, 0xffd75f }, { 222, 0xffd787 },
948 { 223, 0xffd7af }, { 224, 0xffd7d7 }, { 225, 0xffd7ff },
949 { 226, 0xffff00 }, { 227, 0xffff5f }, { 228, 0xffff87 },
950 { 229, 0xffffaf }, { 230, 0xffffd7 }, { 231, 0xffffff },
951 { 232, 0x080808 }, { 233, 0x121212 }, { 234, 0x1c1c1c },
952 { 235, 0x262626 }, { 236, 0x303030 }, { 237, 0x3a3a3a },
953 { 238, 0x444444 }, { 239, 0x4e4e4e }, { 240, 0x585858 },
954 { 241, 0x626262 }, { 242, 0x6c6c6c }, { 243, 0x767676 },
955 { 244, 0x808080 }, { 245, 0x8a8a8a }, { 246, 0x949494 },
956 { 247, 0x9e9e9e }, { 248, 0xa8a8a8 }, { 249, 0xb2b2b2 },
957 { 250, 0xbcbcbc }, { 251, 0xc6c6c6 }, { 252, 0xd0d0d0 },
958 { 253, 0xdadada }, { 254, 0xe4e4e4 }, { 255, 0xeeeeee },
961 /** Calculate the color distance between two colors.
963 * Algorithm taken from UnNetHack which took it from
964 * https://www.compuphase.com/cmetric.htm
968 color_distance(uint32_t rgb1
, uint32_t rgb2
)
970 int r1
= (rgb1
>> 16) & 0xFF;
971 int g1
= (rgb1
>> 8) & 0xFF;
972 int b1
= (rgb1
) & 0xFF;
973 int r2
= (rgb2
>> 16) & 0xFF;
974 int g2
= (rgb2
>> 8) & 0xFF;
975 int b2
= (rgb2
) & 0xFF;
977 int rmean
= (r1
+ r2
) / 2;
981 return ((((512 + rmean
) * r
* r
) >> 8) + 4 * g
* g
982 + (((767 - rmean
) * b
* b
) >> 8));
986 closest_color(uint32 lcolor
, uint32
*closecolor
, uint16
*clridx
)
988 int i
, color_index
= -1, similar
= INT_MAX
, current
;
989 boolean retbool
= FALSE
;
991 for (i
= 0; i
< SIZE(color_256_definitions
); i
++) {
992 /* look for an exact match */
993 if (lcolor
== color_256_definitions
[i
].value
) {
997 /* find a close color match */
998 current
= color_distance(lcolor
, color_256_definitions
[i
].value
);
999 if (current
< similar
) {
1004 if (closecolor
&& clridx
&& color_index
>= 0) {
1005 *closecolor
= color_256_definitions
[color_index
].value
;
1006 *clridx
= color_256_definitions
[color_index
].index
;
1013 get_nhcolor_from_256_index(int idx
)
1015 uint32 retcolor
= NO_COLOR
| NH_BASIC_COLOR
;
1017 if (IndexOk(idx
, color_256_definitions
))
1018 retcolor
= color_256_definitions
[idx
].value
;
1025 count_alt_palette(void)
1027 int clr
, clrcount
= 0;
1029 for (clr
= 0; clr
< CLR_MAX
; ++clr
) {
1030 if (ga
.altpalette
[clr
] != 0U)
1037 alternative_palette(char *op
)
1039 char buf
[BUFSZ
], *c_colorid
, *c_colorval
, *cp
;
1040 int reslt
= 0, coloridx
= CLR_MAX
;
1042 boolean slash
= FALSE
;
1047 Snprintf(buf
, sizeof buf
, "%s", op
);
1048 c_colorval
= (char *) 0;
1049 c_colorid
= cp
= buf
;
1051 if (*cp
== ':' || *cp
== '/') {
1063 /* some sanity checks */
1064 if (c_colorid
&& *c_colorid
== ' ')
1066 if (c_colorval
&& *c_colorval
== ' ')
1069 coloridx
= match_str2clr(c_colorid
, TRUE
);
1071 if (c_colorval
&& coloridx
>= 0 && coloridx
< CLR_MAX
) {
1072 rgb
= rgbstr_to_int32(c_colorval
);
1074 rgb
= alt_color_spec(c_colorval
);
1077 ga
.altpalette
[coloridx
] = (uint32
) rgb
| NH_ALTPALETTE
;
1078 /* use COLORVAL(ga.altpalette[coloridx]) to get
1079 the actual rgb value out of ga.altpalette[] */
1087 change_palette(void)
1091 for (clridx
= 0; clridx
< CLR_MAX
; ++clridx
) {
1092 if (ga
.altpalette
[clridx
] != 0) {
1093 long rgb
= (long) COLORVAL(ga
.altpalette
[clridx
]);
1094 (*windowprocs
.win_change_color
)(clridx
, rgb
, 0);
1100 alt_color_spec(const char *str
)
1102 static NEARDATA
const char oct
[] = "01234567", dec
[] = "0123456789";
1103 /* hexdd[] is defined in decl.c */
1105 const char *dp
, *cp
= str
;
1107 int dcount
, dlimit
= 6;
1108 boolean hexescape
= FALSE
, octescape
= FALSE
;
1110 dcount
= 0; /* for decimal, octal, hexadecimal cases */
1112 (*cp
== '\\' && cp
[1] && (cp
[1] == 'x' || cp
[1] == 'X') && cp
[2]);
1115 (*cp
== '\\' && cp
[1] && (cp
[1] == 'o' || cp
[1] == 'O') && cp
[2]);
1118 if (hexescape
|| octescape
) {
1123 } else if (*cp
== '#' && cp
[1]) {
1130 } else if (!cp
[1]) {
1131 if (strchr(dec
, *cp
) != 0) {
1132 /* simple val, or nothing left for \ to escape */
1140 if (!hexescape
&& !octescape
&& strchr(dec
, *cp
)) {
1141 cval
= (cval
* 10) + (*cp
- '0');
1142 } else if (octescape
&& strchr(oct
, *cp
)) {
1143 cval
= (cval
* 8) + (*cp
- '0');
1144 } else if (hexescape
&& (dp
= strchr(hexdd
, *cp
)) != 0) {
1145 cval
= (cval
* 16) + ((int) (dp
- hexdd
) / 2);
1148 if (++dcount
> dlimit
) {
1155 #endif /* CHANGE_COLOR */