1 /*
2  * Copyright (c)2004 Cat's Eye Technologies.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *
8  *   Redistributions of source code must retain the above copyright
9  *   notice, this list of conditions and the following disclaimer.
10  *
11  *   Redistributions in binary form must reproduce the above copyright
12  *   notice, this list of conditions and the following disclaimer in
13  *   the documentation and/or other materials provided with the
14  *   distribution.
15  *
16  *   Neither the name of Cat's Eye Technologies nor the names of its
17  *   contributors may be used to endorse or promote products derived
18  *   from this software without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
24  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
25  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
26  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
29  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
31  * OF THE POSSIBILITY OF SUCH DAMAGE.
32  */
33 
34 /*
35  * curses_util.c
36  * $Id: curses_util.c,v 1.7 2005/02/08 07:49:03 cpressey Exp $
37  */
38 
39 #include <ctype.h>
40 #include <ncurses.h>
41 #include <panel.h>
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
45 
46 #include "curses_util.h"
47 
48 unsigned int ymax, xmax;
49 int monochrome = 1;
50 int allocated_colors = 0;
51 
52 struct curses_attr {
53 	int pair_no;
54 	int bold;
55 };
56 
57 struct curses_attr colors_tab[CURSES_COLORS_MAX];
58 
59 int colors[8] = {
60 	COLOR_BLACK,
61 	COLOR_RED,
62 	COLOR_GREEN,
63 	COLOR_YELLOW,
64 	COLOR_BLUE,
65 	COLOR_MAGENTA,
66 	COLOR_CYAN,
67 	COLOR_WHITE
68 };
69 
70 /*
71  * If there is an established color pair with the given fg and bg
72  * colors, return it.  Else allocate a new pair with these colors
73  * and return that.
74  */
75 static int
76 curses_colors_find(int fg, int bg)
77 {
78 	int pair_no;
79 	short fge, bge;
80 
81 	for (pair_no = 0;
82 	     pair_no <= allocated_colors && pair_no < COLOR_PAIRS;
83 	     pair_no++) {
84 		pair_content(pair_no, &fge, &bge);
85 		if (fg == fge && bg == bge)
86 			return(pair_no);
87 	}
88 
89 	/*
90 	 * No pair was found, allocate a new one.
91 	 */
92 	if (allocated_colors < (COLOR_PAIRS-1)) {
93 		allocated_colors++;
94 		init_pair(allocated_colors, fg, bg);
95 		return(allocated_colors);
96 	}
97 
98 	/*
99 	 * No space to allocate a new one, return error.
100 	 */
101 	return(-1);
102 }
103 
104 static void
105 curses_colors_cfg(int role, int fg, int bg, int bold)
106 {
107 	int pair_no;
108 
109 	pair_no = curses_colors_find(fg, bg);
110 	if (pair_no != -1) {
111 		colors_tab[role].pair_no = pair_no;
112 		colors_tab[role].bold = bold;
113 	} else {
114 		colors_tab[role].pair_no = 0;
115 		colors_tab[role].bold = bold;
116 	}
117 }
118 
119 void
120 curses_colors_init(int force_monochrome)
121 {
122 	if (!force_monochrome) {
123 		if (has_colors()) {
124 			monochrome = 0;
125 			start_color();
126 		}
127 	}
128 
129 	/*
130 	 * By default, make it look kinda like the default libdialog.
131 	 */
132 	curses_colors_cfg(CURSES_COLORS_NORMAL,    COLOR_BLACK,  COLOR_GREY,  0);
133 	curses_colors_cfg(CURSES_COLORS_BACKDROP,  COLOR_WHITE,  COLOR_BLUE,  0);
134 	curses_colors_cfg(CURSES_COLORS_MENUBAR,   COLOR_BLACK,  COLOR_GREY,  0);
135 	curses_colors_cfg(CURSES_COLORS_STATUSBAR, COLOR_BLACK,  COLOR_GREY,  0);
136 	curses_colors_cfg(CURSES_COLORS_BORDER,	   COLOR_WHITE,  COLOR_GREY,  1);
137 	curses_colors_cfg(CURSES_COLORS_FORMTITLE, COLOR_YELLOW, COLOR_GREY,  1);
138 	curses_colors_cfg(CURSES_COLORS_LABEL,     COLOR_BLACK,  COLOR_GREY,  0);
139 	curses_colors_cfg(CURSES_COLORS_CONTROL,   COLOR_BLACK,  COLOR_GREY,  0);
140 	curses_colors_cfg(CURSES_COLORS_TEXT,      COLOR_BLACK,  COLOR_GREY,  0);
141 	curses_colors_cfg(CURSES_COLORS_FOCUS,     COLOR_WHITE,  COLOR_BLUE,  1);
142 	curses_colors_cfg(CURSES_COLORS_SCROLLAREA,COLOR_GREY,   COLOR_BLACK, 0);
143 	curses_colors_cfg(CURSES_COLORS_SCROLLBAR, COLOR_WHITE,  COLOR_BLUE,  1);
144 	curses_colors_cfg(CURSES_COLORS_ACCEL,     COLOR_WHITE,  COLOR_GREY,  1);
145 	curses_colors_cfg(CURSES_COLORS_ACCELFOCUS,COLOR_YELLOW, COLOR_BLUE,  1);
146 }
147 
148 void
149 curses_colors_set(WINDOW *w, int a)
150 {
151 	if (!monochrome)
152 		wattrset(w, COLOR_PAIR(colors_tab[a].pair_no));
153 	if (colors_tab[a].bold)
154 		wattron(w, A_BOLD);
155 	else
156 		wattroff(w, A_BOLD);
157 }
158 
159 void
160 curses_window_blank(WINDOW *w)
161 {
162 	unsigned int i;
163 
164 	for (i = 0; i <= ymax; i++) {
165 		wmove(w, i, 0);
166 		whline(w, ' ', xmax);
167 	}
168 
169 	wrefresh(w);
170 }
171 
172 void
173 curses_frame_draw(int x, int y, int width, int height)
174 {
175 	int i;
176 
177 	mvaddch(y, x, ACS_ULCORNER);
178 	hline(ACS_HLINE, width - 2);
179 	mvaddch(y, x + width - 1, ACS_URCORNER);
180 
181 	mvaddch(y + height - 1, x, ACS_LLCORNER);
182 	hline(ACS_HLINE, width - 2);
183 	mvaddch(y + height - 1, x + width - 1, ACS_LRCORNER);
184 
185 	move(y + 1, x);
186 	vline(ACS_VLINE, height - 2);
187 
188 	move(y + 1, x + width - 1);
189 	vline(ACS_VLINE, height - 2);
190 
191 	for (i = y + 1; i < y + height - 1; i++) {
192 		move(i, x + 1);
193 		hline(' ', width - 2);
194 	}
195 }
196 
197 void
198 curses_load_backdrop(WINDOW *w, const char *filename)
199 {
200 	FILE *f;
201 	char line[80];
202 	int row = 1;
203 	int my, mx;
204 
205 	getmaxyx(w, my, mx);
206 	wclear(w);
207 	curses_colors_set(w, CURSES_COLORS_BACKDROP);
208 	curses_window_blank(w);
209 
210 	if ((f = fopen(filename, "r")) != NULL) {
211 		while (fgets(line, 79, f) != NULL) {
212 			if (row > my)
213 				break;
214 			if (line[strlen(line) - 1] == '\n')
215 				line[strlen(line) - 1] = '\0';
216 			mvwaddnstr(w, row++, 0, line, mx);
217 		}
218 		fclose(f);
219 	}
220 }
221 
222 void
223 curses_debug_str(const char *s)
224 {
225 	char b[256];
226 
227 	move(1, 0);
228 	sprintf(b, "[%77s]", s);
229 	addstr(b);
230 	refresh();
231 }
232 
233 void
234 curses_debug_int(int i)
235 {
236 	char b[256];
237 
238 	move(1, 0);
239 	sprintf(b, "[%06d]", i);
240 	addstr(b);
241 	refresh();
242 }
243 
244 void
245 curses_debug_key(int i)
246 {
247 	char b[256];
248 
249 	move(1, 0);
250 	sprintf(b, "[%06d] %s", i, keyname(i));
251 	addstr(b);
252 	refresh();
253 }
254 
255 void
256 curses_debug_float(float f)
257 {
258 	char b[256];
259 
260 	move(1, 0);
261 	sprintf(b, "[%09.3f]", f);
262 	addstr(b);
263 	refresh();
264 }
265 
266 /*
267  * Word wrapping.
268  *
269  * text:	The text to word-wrap, as one long string.  Spaces will be
270  *		compressed, but end-of-line characters will be honoured.
271  * line:	A buffer (must be allocated by the caller) to hold a single
272  *		line extracted from text.
273  * width:	The maximum width of a line.
274  * spos:	Pointer to the source position in text.  Should be initially
275  *		set to zero, and retained between calls to this function.
276  * Returns:	A boolean indicating whether the end of text was reached.
277  *		Typically this function should be called repeatedly until
278  *		it returns true.
279  */
280 int
281 extract_wrapped_line(const char *text, char *line, int width, int *spos)
282 {
283 	int dpos = 0;
284 	int saved_spos, saved_dpos;
285 
286 	for (;;) {
287 		/*
288 		 * Skip over whitespace.  If we find a newline or the
289 		 * end of the text, return a blank line.  Leave *spos
290 		 * at the position of the 1st non-whitespace character.
291 		 */
292 		while (isspace(text[*spos]) && text[*spos] != '\0') {
293 			if (text[*spos] == '\n') {
294 				line[dpos] = '\0';
295 				(*spos)++;
296 				return(0);
297 			}
298 			(*spos)++;
299 		}
300 
301 		/*
302 		 * Save start position and destination position.
303 		 */
304 		saved_spos = *spos;
305 		saved_dpos = dpos;
306 
307 		/*
308 		 * Read a word from *spos onward.
309 		 */
310 		while (!isspace(text[*spos]) &&
311 		       text[*spos] != '\0' &&
312 		       dpos < width) {
313 			line[dpos++] = text[(*spos)++];
314 		}
315 
316 		if (text[*spos] == '\0') {
317 			/*
318 			 * End of string - return this word as the last.
319 			 */
320 			line[dpos] = '\0';
321 			return(1);
322 		} else if (dpos >= width) {
323 			/*
324 			 * Last word is too long to fit on this line.
325 			 */
326 			if (dpos - saved_dpos >= width) {
327 				/*
328 				 * In fact, it's too long to fit on any line!
329 				 * Truncate it.
330 				 */
331 				line[width - 1] = '\0';
332 				*spos = saved_spos + (dpos - saved_dpos);
333 				return(0);
334 			} else {
335 				/*
336 				 * Save it for the next pass.
337 				 */
338 				*spos = saved_spos;
339 				line[saved_dpos - 1] = '\0';
340 				return(0);
341 			}
342 		} else {
343 			line[dpos++] = ' ';
344 		}
345 	}
346 }
347