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