1 /*
2  * Samba Unix/Linux SMB client library
3  * Registry Editor
4  * Copyright (C) Christopher Davis 2014
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include "regedit_list.h"
21 #include "regedit.h"
22 
23 struct multilist {
24 	WINDOW *window;
25 	WINDOW *pad;
26 
27 	unsigned window_height;
28 	unsigned window_width;
29 	unsigned start_row;
30 	unsigned cursor_row;
31 
32 	unsigned ncols;
33 	struct multilist_column *columns;
34 
35 	const void *data;
36 	unsigned nrows;
37 	const void *current_row;
38 	const struct multilist_accessors *cb;
39 };
40 
41 /* data getters */
data_get_first_row(struct multilist * list)42 static const void *data_get_first_row(struct multilist *list)
43 {
44 	SMB_ASSERT(list->cb->get_first_row);
45 	return list->cb->get_first_row(list->data);
46 }
47 
data_get_next_row(struct multilist * list,const void * row)48 static const void *data_get_next_row(struct multilist *list, const void *row)
49 {
50 	SMB_ASSERT(list->cb->get_next_row);
51 	return list->cb->get_next_row(list->data, row);
52 }
53 
data_get_prev_row(struct multilist * list,const void * row)54 static const void *data_get_prev_row(struct multilist *list, const void *row)
55 {
56 	const void *tmp, *next;
57 
58 	if (list->cb->get_prev_row) {
59 		return list->cb->get_prev_row(list->data, row);
60 	}
61 
62 	tmp = data_get_first_row(list);
63 	if (tmp == row) {
64 		return NULL;
65 	}
66 
67 	for (; tmp && (next = data_get_next_row(list, tmp)) != row;
68 	     tmp = next) {
69 	}
70 
71 	SMB_ASSERT(tmp != NULL);
72 
73 	return tmp;
74 }
75 
data_get_row_count(struct multilist * list)76 static unsigned data_get_row_count(struct multilist *list)
77 {
78 	unsigned i;
79 	const void *row;
80 
81 	if (list->cb->get_row_count)
82 		return list->cb->get_row_count(list->data);
83 
84 	for (i = 0, row = data_get_first_row(list);
85 	     row != NULL;
86 	     ++i, row = data_get_next_row(list, row)) {
87 	}
88 
89 	return i;
90 }
91 
data_get_row_n(struct multilist * list,size_t n)92 static const void *data_get_row_n(struct multilist *list, size_t n)
93 {
94 	unsigned i;
95 	const void *row;
96 
97 	if (list->cb->get_row_n)
98 		return list->cb->get_row_n(list->data, n);
99 
100 	for (i = 0, row = data_get_first_row(list);
101 	     i < n && row != NULL;
102 	     ++i, row = data_get_next_row(list, row)) {
103 	}
104 
105 	return row;
106 }
107 
data_get_column_header(struct multilist * list,unsigned col)108 static const char *data_get_column_header(struct multilist *list, unsigned col)
109 {
110 	SMB_ASSERT(list->cb->get_column_header);
111 	return list->cb->get_column_header(list->data, col);
112 }
113 
data_get_item_label(struct multilist * list,const void * row,unsigned col)114 static const char *data_get_item_label(struct multilist *list, const void *row,
115 				       unsigned col)
116 {
117 	SMB_ASSERT(list->cb->get_item_label);
118 	return list->cb->get_item_label(row, col);
119 }
120 
data_get_item_prefix(struct multilist * list,const void * row,unsigned col)121 static const char *data_get_item_prefix(struct multilist *list, const void *row,
122 					unsigned col)
123 {
124 	if (list->cb->get_item_prefix)
125 		return list->cb->get_item_prefix(row, col);
126 	return "";
127 }
128 
multilist_free(struct multilist * list)129 static int multilist_free(struct multilist *list)
130 {
131 	if (list->pad) {
132 		delwin(list->pad);
133 	}
134 
135 	return 0;
136 }
137 
multilist_new(TALLOC_CTX * ctx,WINDOW * window,const struct multilist_accessors * cb,unsigned ncol)138 struct multilist *multilist_new(TALLOC_CTX *ctx, WINDOW *window,
139 				const struct multilist_accessors *cb,
140 				unsigned ncol)
141 {
142 	struct multilist *list;
143 
144 	SMB_ASSERT(ncol > 0);
145 
146 	list = talloc_zero(ctx, struct multilist);
147 	if (list == NULL) {
148 		return NULL;
149 	}
150 	talloc_set_destructor(list, multilist_free);
151 
152 	list->cb = cb;
153 	list->ncols = ncol;
154 	list->columns = talloc_zero_array(list, struct multilist_column, ncol);
155 	if (list->columns == NULL) {
156 		talloc_free(list);
157 		return NULL;
158 	}
159 	multilist_set_window(list, window);
160 
161 	return list;
162 }
163 
multilist_column_config(struct multilist * list,unsigned col)164 struct multilist_column *multilist_column_config(struct multilist *list,
165 						 unsigned col)
166 {
167 	SMB_ASSERT(col < list->ncols);
168 	return &list->columns[col];
169 }
170 
put_padding(WINDOW * win,size_t col_width,size_t item_len)171 static void put_padding(WINDOW *win, size_t col_width, size_t item_len)
172 {
173 	size_t amt;
174 
175 	SMB_ASSERT(item_len <= col_width);
176 
177 	amt = col_width - item_len;
178 	while (amt--) {
179 		waddch(win, ' ');
180 	}
181 }
182 
put_item(struct multilist * list,WINDOW * win,unsigned col,const char * item,int attr)183 static void put_item(struct multilist *list, WINDOW *win, unsigned col,
184 		     const char *item, int attr)
185 {
186 	bool append_sep = true;
187 	unsigned i;
188 	size_t len;
189 	struct multilist_column *col_info;
190 	bool trim = false;
191 
192 	SMB_ASSERT(col < list->ncols);
193 	SMB_ASSERT(item != NULL);
194 
195 	if (col == list->ncols - 1) {
196 		append_sep = false;
197 	}
198 	col_info = &list->columns[col];
199 
200 	len = strlen(item);
201 	if (len > col_info->width) {
202 		len = col_info->width;
203 		trim = true;
204 	}
205 
206 	if (col_info->align_right) {
207 		put_padding(win, col_info->width, len);
208 	}
209 	for (i = 0; i < len; ++i) {
210 		if (i == len - 1 && trim) {
211 			waddch(win, '~' | attr);
212 		} else {
213 			waddch(win, item[i] | attr);
214 		}
215 	}
216 	if (!col_info->align_right) {
217 		put_padding(win, col_info->width, len);
218 	}
219 
220 	if (append_sep) {
221 		waddch(win, ' ');
222 		waddch(win, '|');
223 		waddch(win, ' ');
224 	}
225 }
226 
put_header(struct multilist * list)227 static void put_header(struct multilist *list)
228 {
229 	unsigned col;
230 	const char *header;
231 
232 	if (!list->cb->get_column_header) {
233 		return;
234 	}
235 
236 	wmove(list->window, 0, 0);
237 	for (col = 0; col < list->ncols; ++col) {
238 		header = data_get_column_header(list, col);
239 		SMB_ASSERT(header != NULL);
240 		put_item(list, list->window, col, header,
241 			 A_BOLD | COLOR_PAIR(PAIR_YELLOW_BLUE));
242 	}
243 }
244 
put_data(struct multilist * list)245 static WERROR put_data(struct multilist *list)
246 {
247 	const void *row;
248 	int ypos;
249 	unsigned col;
250 	const char *prefix, *item;
251 	char *tmp;
252 
253 	for (ypos = 0, row = data_get_first_row(list);
254 	     row != NULL;
255 	     row = data_get_next_row(list, row), ++ypos) {
256 		wmove(list->pad, ypos, 0);
257 		for (col = 0; col < list->ncols; ++col) {
258 			prefix = data_get_item_prefix(list, row, col);
259 			SMB_ASSERT(prefix != NULL);
260 			item = data_get_item_label(list, row, col);
261 			SMB_ASSERT(item != NULL);
262 			tmp = talloc_asprintf(list, "%s%s", prefix, item);
263 			if (tmp == NULL) {
264 				return WERR_NOT_ENOUGH_MEMORY;
265 			}
266 			put_item(list, list->pad, col, tmp, 0);
267 			talloc_free(tmp);
268 		}
269 	}
270 
271 	return WERR_OK;
272 }
273 
274 #define MIN_WIDTH 3
find_widest_column(struct multilist * list)275 static struct multilist_column *find_widest_column(struct multilist *list)
276 {
277 	unsigned col;
278 	struct multilist_column *colp;
279 
280 	SMB_ASSERT(list->ncols > 0);
281 	colp = &list->columns[0];
282 
283 	for (col = 1; col < list->ncols; ++col) {
284 		if (list->columns[col].width > colp->width) {
285 			colp = &list->columns[col];
286 		}
287 	}
288 
289 	if (colp->width < MIN_WIDTH) {
290 		return NULL;
291 	}
292 
293 	return colp;
294 }
295 
calc_column_widths(struct multilist * list)296 static WERROR calc_column_widths(struct multilist *list)
297 {
298 	const void *row;
299 	unsigned col;
300 	size_t len;
301 	const char *item;
302 	size_t width, total_width, overflow;
303 	struct multilist_column *colp;
304 
305 	/* calculate the maximum widths for each column */
306 	for (col = 0; col < list->ncols; ++col) {
307 		len = 0;
308 		if (list->cb->get_column_header) {
309 			item = data_get_column_header(list, col);
310 			len = strlen(item);
311 		}
312 		list->columns[col].width = len;
313 	}
314 
315 	for (row = data_get_first_row(list);
316 	     row != NULL;
317 	     row = data_get_next_row(list, row)) {
318 		for (col = 0; col < list->ncols; ++col) {
319 			item = data_get_item_prefix(list, row, col);
320 			SMB_ASSERT(item != NULL);
321 			len = strlen(item);
322 
323 			item = data_get_item_label(list, row, col);
324 			SMB_ASSERT(item != NULL);
325 			len += strlen(item);
326 			if (len > list->columns[col].width) {
327 				list->columns[col].width = len;
328 			}
329 		}
330 	}
331 
332 	/* calculate row width */
333 	for (width = 0, col = 0; col < list->ncols; ++col) {
334 		width += list->columns[col].width;
335 	}
336 	/* width including column spacing and separations */
337 	total_width = width + (list->ncols - 1) * 3;
338 	/* if everything fits, we're done */
339 	if (total_width <= list->window_width) {
340 		return WERR_OK;
341 	}
342 
343 	overflow = total_width - list->window_width;
344 
345 	/* attempt to trim as much as possible to fit all the columns to
346 	   the window */
347 	while (overflow && (colp = find_widest_column(list))) {
348 		colp->width--;
349 		overflow--;
350 	}
351 
352 	return WERR_OK;
353 }
354 
highlight_current_row(struct multilist * list)355 static void highlight_current_row(struct multilist *list)
356 {
357 	mvwchgat(list->pad, list->cursor_row, 0, -1, A_REVERSE, 0, NULL);
358 }
359 
unhighlight_current_row(struct multilist * list)360 static void unhighlight_current_row(struct multilist *list)
361 {
362 	mvwchgat(list->pad, list->cursor_row, 0, -1, A_NORMAL, 0, NULL);
363 }
364 
multilist_get_data(struct multilist * list)365 const void *multilist_get_data(struct multilist *list)
366 {
367 	return list->data;
368 }
369 
multilist_set_data(struct multilist * list,const void * data)370 WERROR multilist_set_data(struct multilist *list, const void *data)
371 {
372 	WERROR rv;
373 
374 	SMB_ASSERT(list->window != NULL);
375 	list->data = data;
376 
377 	calc_column_widths(list);
378 
379 	if (list->pad) {
380 		delwin(list->pad);
381 	}
382 	/* construct a pad that is exactly the width of the window, and
383 	   as tall as required to fit all data rows. */
384 	list->nrows = data_get_row_count(list);
385 	list->pad = newpad(MAX(list->nrows, 1), list->window_width);
386 	if (list->pad == NULL) {
387 		return WERR_NOT_ENOUGH_MEMORY;
388 	}
389 
390 	/* add the column headers to the window and render all rows to
391 	   the pad. */
392 	werase(list->window);
393 	put_header(list);
394 	rv = put_data(list);
395 	if (!W_ERROR_IS_OK(rv)) {
396 		return rv;
397 	}
398 
399 	/* initialize the cursor */
400 	list->start_row = 0;
401 	list->cursor_row = 0;
402 	list->current_row = data_get_first_row(list);
403 	highlight_current_row(list);
404 
405 	return WERR_OK;
406 }
407 
get_window_height(struct multilist * list)408 static int get_window_height(struct multilist *list)
409 {
410 	int height;
411 
412 	height = list->window_height;
413 	if (list->cb->get_column_header) {
414 		height--;
415 	}
416 
417 	return height;
418 }
419 
fix_start_row(struct multilist * list)420 static void fix_start_row(struct multilist *list)
421 {
422 	int height;
423 
424 	/* adjust start_row so that the cursor appears on the screen */
425 
426 	height = get_window_height(list);
427 	if (list->cursor_row < list->start_row) {
428 		list->start_row = list->cursor_row;
429 	} else if (list->cursor_row >= list->start_row + height) {
430 		list->start_row = list->cursor_row - height + 1;
431 	}
432 	if (list->nrows > height && list->nrows - list->start_row < height) {
433 		list->start_row = list->nrows - height;
434 	}
435 }
436 
multilist_set_window(struct multilist * list,WINDOW * window)437 WERROR multilist_set_window(struct multilist *list, WINDOW *window)
438 {
439 	int maxy, maxx;
440 	bool rerender = false;
441 
442 	getmaxyx(window, maxy, maxx);
443 
444 	/* rerender pad if window width is different. */
445 	if (list->data && maxx != list->window_width) {
446 		rerender = true;
447 	}
448 
449 	list->window = window;
450 	list->window_width = maxx;
451 	list->window_height = maxy;
452 	list->start_row = 0;
453 	if (rerender) {
454 		const void *row = multilist_get_current_row(list);
455 		WERROR rv = multilist_set_data(list, list->data);
456 		if (W_ERROR_IS_OK(rv) && row) {
457 			multilist_set_current_row(list, row);
458 		}
459 		return rv;
460 	} else {
461 		put_header(list);
462 		fix_start_row(list);
463 	}
464 
465 	return WERR_OK;
466 }
467 
multilist_refresh(struct multilist * list)468 void multilist_refresh(struct multilist *list)
469 {
470 	int window_start_row, height;
471 
472 	if (list->nrows == 0) {
473 		return;
474 	}
475 
476 	/* copy from pad, starting at start_row, to the window, accounting
477 	   for the column header (if present). */
478 	height = MIN(list->window_height, list->nrows);
479 	window_start_row = 0;
480 	if (list->cb->get_column_header) {
481 		window_start_row = 1;
482 		if (height < list->window_height) {
483 			height++;
484 		}
485 	}
486 	copywin(list->pad, list->window, list->start_row, 0,
487 		window_start_row, 0, height - 1, list->window_width - 1,
488 		false);
489 }
490 
multilist_driver(struct multilist * list,int c)491 void multilist_driver(struct multilist *list, int c)
492 {
493 	unsigned page;
494 	const void *tmp = NULL;
495 
496 	if (list->nrows == 0) {
497 		return;
498 	}
499 
500 	switch (c) {
501 	case ML_CURSOR_UP:
502 		if (list->cursor_row == 0) {
503 			return;
504 		}
505 		unhighlight_current_row(list);
506 		list->cursor_row--;
507 		tmp = data_get_prev_row(list, list->current_row);
508 		break;
509 	case ML_CURSOR_DOWN:
510 		if (list->cursor_row == list->nrows - 1) {
511 			return;
512 		}
513 		unhighlight_current_row(list);
514 		list->cursor_row++;
515 		tmp = data_get_next_row(list, list->current_row);
516 		break;
517 	case ML_CURSOR_PGUP:
518 		if (list->cursor_row == 0) {
519 			return;
520 		}
521 		unhighlight_current_row(list);
522 		page = get_window_height(list);
523 		if (page > list->cursor_row) {
524 			list->cursor_row = 0;
525 		} else {
526 			list->cursor_row -= page;
527 			list->start_row -= page;
528 		}
529 		tmp = data_get_row_n(list, list->cursor_row);
530 		break;
531 	case ML_CURSOR_PGDN:
532 		if (list->cursor_row == list->nrows - 1) {
533 			return;
534 		}
535 		unhighlight_current_row(list);
536 		page = get_window_height(list);
537 		if (page > list->nrows - list->cursor_row - 1) {
538 			list->cursor_row = list->nrows - 1;
539 		} else {
540 			list->cursor_row += page;
541 			list->start_row += page;
542 		}
543 		tmp = data_get_row_n(list, list->cursor_row);
544 		break;
545 	case ML_CURSOR_HOME:
546 		if (list->cursor_row == 0) {
547 			return;
548 		}
549 		unhighlight_current_row(list);
550 		list->cursor_row = 0;
551 		tmp = data_get_row_n(list, list->cursor_row);
552 		break;
553 	case ML_CURSOR_END:
554 		if (list->cursor_row == list->nrows - 1) {
555 			return;
556 		}
557 		unhighlight_current_row(list);
558 		list->cursor_row = list->nrows - 1;
559 		tmp = data_get_row_n(list, list->cursor_row);
560 		break;
561 	}
562 
563 	SMB_ASSERT(tmp);
564 	list->current_row = tmp;
565 	highlight_current_row(list);
566 	fix_start_row(list);
567 }
568 
multilist_get_current_row(struct multilist * list)569 const void *multilist_get_current_row(struct multilist *list)
570 {
571 	return list->current_row;
572 }
573 
multilist_set_current_row(struct multilist * list,const void * row)574 void multilist_set_current_row(struct multilist *list, const void *row)
575 {
576 	unsigned i;
577 	const void *tmp;
578 
579 	for (i = 0, tmp = data_get_first_row(list);
580 	     tmp != NULL;
581 	     ++i, tmp = data_get_next_row(list, tmp)) {
582 		if (tmp == row) {
583 			unhighlight_current_row(list);
584 			list->cursor_row = i;
585 			list->current_row = row;
586 			highlight_current_row(list);
587 			fix_start_row(list);
588 			return;
589 		}
590 	}
591 }
592