dbwrap_tool: We don't do "listwatchers" anymore
[garming/samba-autobuild/.git] / source3 / utils / regedit_list.c
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 */
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
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
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
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
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
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
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
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
129 static int multilist_free(struct multilist *list)
130 {
131         if (list->pad) {
132                 delwin(list->pad);
133         }
134
135         return 0;
136 }
137
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
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
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
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
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
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
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
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
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
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
365 const void *multilist_get_data(struct multilist *list)
366 {
367         return list->data;
368 }
369
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
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
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
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
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
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
569 const void *multilist_get_current_row(struct multilist *list)
570 {
571         return list->current_row;
572 }
573
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 }