mirror of
https://github.com/samba-team/samba.git
synced 2024-12-27 03:21:53 +03:00
b00f30c0ac
Guenther Signed-off-by: Guenther Deschner <gd@samba.org> Reviewed-by: Jeremy Allison <jra@samba.org>
592 lines
13 KiB
C
592 lines
13 KiB
C
/*
|
|
* Samba Unix/Linux SMB client library
|
|
* Registry Editor
|
|
* Copyright (C) Christopher Davis 2014
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "regedit_list.h"
|
|
#include "regedit.h"
|
|
|
|
struct multilist {
|
|
WINDOW *window;
|
|
WINDOW *pad;
|
|
|
|
unsigned window_height;
|
|
unsigned window_width;
|
|
unsigned start_row;
|
|
unsigned cursor_row;
|
|
|
|
unsigned ncols;
|
|
struct multilist_column *columns;
|
|
|
|
const void *data;
|
|
unsigned nrows;
|
|
const void *current_row;
|
|
const struct multilist_accessors *cb;
|
|
};
|
|
|
|
/* data getters */
|
|
static const void *data_get_first_row(struct multilist *list)
|
|
{
|
|
SMB_ASSERT(list->cb->get_first_row);
|
|
return list->cb->get_first_row(list->data);
|
|
}
|
|
|
|
static const void *data_get_next_row(struct multilist *list, const void *row)
|
|
{
|
|
SMB_ASSERT(list->cb->get_next_row);
|
|
return list->cb->get_next_row(list->data, row);
|
|
}
|
|
|
|
static const void *data_get_prev_row(struct multilist *list, const void *row)
|
|
{
|
|
const void *tmp, *next;
|
|
|
|
if (list->cb->get_prev_row) {
|
|
return list->cb->get_prev_row(list->data, row);
|
|
}
|
|
|
|
tmp = data_get_first_row(list);
|
|
if (tmp == row) {
|
|
return NULL;
|
|
}
|
|
|
|
for (; tmp && (next = data_get_next_row(list, tmp)) != row;
|
|
tmp = next) {
|
|
}
|
|
|
|
SMB_ASSERT(tmp != NULL);
|
|
|
|
return tmp;
|
|
}
|
|
|
|
static unsigned data_get_row_count(struct multilist *list)
|
|
{
|
|
unsigned i;
|
|
const void *row;
|
|
|
|
if (list->cb->get_row_count)
|
|
return list->cb->get_row_count(list->data);
|
|
|
|
for (i = 0, row = data_get_first_row(list);
|
|
row != NULL;
|
|
++i, row = data_get_next_row(list, row)) {
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
static const void *data_get_row_n(struct multilist *list, size_t n)
|
|
{
|
|
unsigned i;
|
|
const void *row;
|
|
|
|
if (list->cb->get_row_n)
|
|
return list->cb->get_row_n(list->data, n);
|
|
|
|
for (i = 0, row = data_get_first_row(list);
|
|
i < n && row != NULL;
|
|
++i, row = data_get_next_row(list, row)) {
|
|
}
|
|
|
|
return row;
|
|
}
|
|
|
|
static const char *data_get_column_header(struct multilist *list, unsigned col)
|
|
{
|
|
SMB_ASSERT(list->cb->get_column_header);
|
|
return list->cb->get_column_header(list->data, col);
|
|
}
|
|
|
|
static const char *data_get_item_label(struct multilist *list, const void *row,
|
|
unsigned col)
|
|
{
|
|
SMB_ASSERT(list->cb->get_item_label);
|
|
return list->cb->get_item_label(row, col);
|
|
}
|
|
|
|
static const char *data_get_item_prefix(struct multilist *list, const void *row,
|
|
unsigned col)
|
|
{
|
|
if (list->cb->get_item_prefix)
|
|
return list->cb->get_item_prefix(row, col);
|
|
return "";
|
|
}
|
|
|
|
static int multilist_free(struct multilist *list)
|
|
{
|
|
if (list->pad) {
|
|
delwin(list->pad);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct multilist *multilist_new(TALLOC_CTX *ctx, WINDOW *window,
|
|
const struct multilist_accessors *cb,
|
|
unsigned ncol)
|
|
{
|
|
struct multilist *list;
|
|
|
|
SMB_ASSERT(ncol > 0);
|
|
|
|
list = talloc_zero(ctx, struct multilist);
|
|
if (list == NULL) {
|
|
return NULL;
|
|
}
|
|
talloc_set_destructor(list, multilist_free);
|
|
|
|
list->cb = cb;
|
|
list->ncols = ncol;
|
|
list->columns = talloc_zero_array(list, struct multilist_column, ncol);
|
|
if (list->columns == NULL) {
|
|
talloc_free(list);
|
|
return NULL;
|
|
}
|
|
multilist_set_window(list, window);
|
|
|
|
return list;
|
|
}
|
|
|
|
struct multilist_column *multilist_column_config(struct multilist *list,
|
|
unsigned col)
|
|
{
|
|
SMB_ASSERT(col < list->ncols);
|
|
return &list->columns[col];
|
|
}
|
|
|
|
static void put_padding(WINDOW *win, size_t col_width, size_t item_len)
|
|
{
|
|
size_t amt;
|
|
|
|
SMB_ASSERT(item_len <= col_width);
|
|
|
|
amt = col_width - item_len;
|
|
while (amt--) {
|
|
waddch(win, ' ');
|
|
}
|
|
}
|
|
|
|
static void put_item(struct multilist *list, WINDOW *win, unsigned col,
|
|
const char *item, int attr)
|
|
{
|
|
bool append_sep = true;
|
|
unsigned i;
|
|
size_t len;
|
|
struct multilist_column *col_info;
|
|
bool trim = false;
|
|
|
|
SMB_ASSERT(col < list->ncols);
|
|
SMB_ASSERT(item != NULL);
|
|
|
|
if (col == list->ncols - 1) {
|
|
append_sep = false;
|
|
}
|
|
col_info = &list->columns[col];
|
|
|
|
len = strlen(item);
|
|
if (len > col_info->width) {
|
|
len = col_info->width;
|
|
trim = true;
|
|
}
|
|
|
|
if (col_info->align_right) {
|
|
put_padding(win, col_info->width, len);
|
|
}
|
|
for (i = 0; i < len; ++i) {
|
|
if (i == len - 1 && trim) {
|
|
waddch(win, '~' | attr);
|
|
} else {
|
|
waddch(win, item[i] | attr);
|
|
}
|
|
}
|
|
if (!col_info->align_right) {
|
|
put_padding(win, col_info->width, len);
|
|
}
|
|
|
|
if (append_sep) {
|
|
waddch(win, ' ');
|
|
waddch(win, '|');
|
|
waddch(win, ' ');
|
|
}
|
|
}
|
|
|
|
static void put_header(struct multilist *list)
|
|
{
|
|
unsigned col;
|
|
const char *header;
|
|
|
|
if (!list->cb->get_column_header) {
|
|
return;
|
|
}
|
|
|
|
wmove(list->window, 0, 0);
|
|
for (col = 0; col < list->ncols; ++col) {
|
|
header = data_get_column_header(list, col);
|
|
SMB_ASSERT(header != NULL);
|
|
put_item(list, list->window, col, header,
|
|
A_BOLD | COLOR_PAIR(PAIR_YELLOW_BLUE));
|
|
}
|
|
}
|
|
|
|
static WERROR put_data(struct multilist *list)
|
|
{
|
|
const void *row;
|
|
int ypos;
|
|
unsigned col;
|
|
const char *prefix, *item;
|
|
char *tmp;
|
|
|
|
for (ypos = 0, row = data_get_first_row(list);
|
|
row != NULL;
|
|
row = data_get_next_row(list, row), ++ypos) {
|
|
wmove(list->pad, ypos, 0);
|
|
for (col = 0; col < list->ncols; ++col) {
|
|
prefix = data_get_item_prefix(list, row, col);
|
|
SMB_ASSERT(prefix != NULL);
|
|
item = data_get_item_label(list, row, col);
|
|
SMB_ASSERT(item != NULL);
|
|
tmp = talloc_asprintf(list, "%s%s", prefix, item);
|
|
if (tmp == NULL) {
|
|
return WERR_NOT_ENOUGH_MEMORY;
|
|
}
|
|
put_item(list, list->pad, col, tmp, 0);
|
|
talloc_free(tmp);
|
|
}
|
|
}
|
|
|
|
return WERR_OK;
|
|
}
|
|
|
|
#define MIN_WIDTH 3
|
|
static struct multilist_column *find_widest_column(struct multilist *list)
|
|
{
|
|
unsigned col;
|
|
struct multilist_column *colp;
|
|
|
|
SMB_ASSERT(list->ncols > 0);
|
|
colp = &list->columns[0];
|
|
|
|
for (col = 1; col < list->ncols; ++col) {
|
|
if (list->columns[col].width > colp->width) {
|
|
colp = &list->columns[col];
|
|
}
|
|
}
|
|
|
|
if (colp->width < MIN_WIDTH) {
|
|
return NULL;
|
|
}
|
|
|
|
return colp;
|
|
}
|
|
|
|
static WERROR calc_column_widths(struct multilist *list)
|
|
{
|
|
const void *row;
|
|
unsigned col;
|
|
size_t len;
|
|
const char *item;
|
|
size_t width, total_width, overflow;
|
|
struct multilist_column *colp;
|
|
|
|
/* calculate the maximum widths for each column */
|
|
for (col = 0; col < list->ncols; ++col) {
|
|
len = 0;
|
|
if (list->cb->get_column_header) {
|
|
item = data_get_column_header(list, col);
|
|
len = strlen(item);
|
|
}
|
|
list->columns[col].width = len;
|
|
}
|
|
|
|
for (row = data_get_first_row(list);
|
|
row != NULL;
|
|
row = data_get_next_row(list, row)) {
|
|
for (col = 0; col < list->ncols; ++col) {
|
|
item = data_get_item_prefix(list, row, col);
|
|
SMB_ASSERT(item != NULL);
|
|
len = strlen(item);
|
|
|
|
item = data_get_item_label(list, row, col);
|
|
SMB_ASSERT(item != NULL);
|
|
len += strlen(item);
|
|
if (len > list->columns[col].width) {
|
|
list->columns[col].width = len;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* calculate row width */
|
|
for (width = 0, col = 0; col < list->ncols; ++col) {
|
|
width += list->columns[col].width;
|
|
}
|
|
/* width including column spacing and separations */
|
|
total_width = width + (list->ncols - 1) * 3;
|
|
/* if everything fits, we're done */
|
|
if (total_width <= list->window_width) {
|
|
return WERR_OK;
|
|
}
|
|
|
|
overflow = total_width - list->window_width;
|
|
|
|
/* attempt to trim as much as possible to fit all the columns to
|
|
the window */
|
|
while (overflow && (colp = find_widest_column(list))) {
|
|
colp->width--;
|
|
overflow--;
|
|
}
|
|
|
|
return WERR_OK;
|
|
}
|
|
|
|
static void highlight_current_row(struct multilist *list)
|
|
{
|
|
mvwchgat(list->pad, list->cursor_row, 0, -1, A_REVERSE, 0, NULL);
|
|
}
|
|
|
|
static void unhighlight_current_row(struct multilist *list)
|
|
{
|
|
mvwchgat(list->pad, list->cursor_row, 0, -1, A_NORMAL, 0, NULL);
|
|
}
|
|
|
|
const void *multilist_get_data(struct multilist *list)
|
|
{
|
|
return list->data;
|
|
}
|
|
|
|
WERROR multilist_set_data(struct multilist *list, const void *data)
|
|
{
|
|
WERROR rv;
|
|
|
|
SMB_ASSERT(list->window != NULL);
|
|
list->data = data;
|
|
|
|
calc_column_widths(list);
|
|
|
|
if (list->pad) {
|
|
delwin(list->pad);
|
|
}
|
|
/* construct a pad that is exactly the width of the window, and
|
|
as tall as required to fit all data rows. */
|
|
list->nrows = data_get_row_count(list);
|
|
list->pad = newpad(MAX(list->nrows, 1), list->window_width);
|
|
if (list->pad == NULL) {
|
|
return WERR_NOT_ENOUGH_MEMORY;
|
|
}
|
|
|
|
/* add the column headers to the window and render all rows to
|
|
the pad. */
|
|
werase(list->window);
|
|
put_header(list);
|
|
rv = put_data(list);
|
|
if (!W_ERROR_IS_OK(rv)) {
|
|
return rv;
|
|
}
|
|
|
|
/* initialize the cursor */
|
|
list->start_row = 0;
|
|
list->cursor_row = 0;
|
|
list->current_row = data_get_first_row(list);
|
|
highlight_current_row(list);
|
|
|
|
return WERR_OK;
|
|
}
|
|
|
|
static int get_window_height(struct multilist *list)
|
|
{
|
|
int height;
|
|
|
|
height = list->window_height;
|
|
if (list->cb->get_column_header) {
|
|
height--;
|
|
}
|
|
|
|
return height;
|
|
}
|
|
|
|
static void fix_start_row(struct multilist *list)
|
|
{
|
|
int height;
|
|
|
|
/* adjust start_row so that the cursor appears on the screen */
|
|
|
|
height = get_window_height(list);
|
|
if (list->cursor_row < list->start_row) {
|
|
list->start_row = list->cursor_row;
|
|
} else if (list->cursor_row >= list->start_row + height) {
|
|
list->start_row = list->cursor_row - height + 1;
|
|
}
|
|
if (list->nrows > height && list->nrows - list->start_row < height) {
|
|
list->start_row = list->nrows - height;
|
|
}
|
|
}
|
|
|
|
WERROR multilist_set_window(struct multilist *list, WINDOW *window)
|
|
{
|
|
int maxy, maxx;
|
|
bool rerender = false;
|
|
|
|
getmaxyx(window, maxy, maxx);
|
|
|
|
/* rerender pad if window width is different. */
|
|
if (list->data && maxx != list->window_width) {
|
|
rerender = true;
|
|
}
|
|
|
|
list->window = window;
|
|
list->window_width = maxx;
|
|
list->window_height = maxy;
|
|
list->start_row = 0;
|
|
if (rerender) {
|
|
const void *row = multilist_get_current_row(list);
|
|
WERROR rv = multilist_set_data(list, list->data);
|
|
if (W_ERROR_IS_OK(rv) && row) {
|
|
multilist_set_current_row(list, row);
|
|
}
|
|
return rv;
|
|
} else {
|
|
put_header(list);
|
|
fix_start_row(list);
|
|
}
|
|
|
|
return WERR_OK;
|
|
}
|
|
|
|
void multilist_refresh(struct multilist *list)
|
|
{
|
|
int window_start_row, height;
|
|
|
|
if (list->nrows == 0) {
|
|
return;
|
|
}
|
|
|
|
/* copy from pad, starting at start_row, to the window, accounting
|
|
for the column header (if present). */
|
|
height = MIN(list->window_height, list->nrows);
|
|
window_start_row = 0;
|
|
if (list->cb->get_column_header) {
|
|
window_start_row = 1;
|
|
if (height < list->window_height) {
|
|
height++;
|
|
}
|
|
}
|
|
copywin(list->pad, list->window, list->start_row, 0,
|
|
window_start_row, 0, height - 1, list->window_width - 1,
|
|
false);
|
|
}
|
|
|
|
void multilist_driver(struct multilist *list, int c)
|
|
{
|
|
unsigned page;
|
|
const void *tmp = NULL;
|
|
|
|
if (list->nrows == 0) {
|
|
return;
|
|
}
|
|
|
|
switch (c) {
|
|
case ML_CURSOR_UP:
|
|
if (list->cursor_row == 0) {
|
|
return;
|
|
}
|
|
unhighlight_current_row(list);
|
|
list->cursor_row--;
|
|
tmp = data_get_prev_row(list, list->current_row);
|
|
break;
|
|
case ML_CURSOR_DOWN:
|
|
if (list->cursor_row == list->nrows - 1) {
|
|
return;
|
|
}
|
|
unhighlight_current_row(list);
|
|
list->cursor_row++;
|
|
tmp = data_get_next_row(list, list->current_row);
|
|
break;
|
|
case ML_CURSOR_PGUP:
|
|
if (list->cursor_row == 0) {
|
|
return;
|
|
}
|
|
unhighlight_current_row(list);
|
|
page = get_window_height(list);
|
|
if (page > list->cursor_row) {
|
|
list->cursor_row = 0;
|
|
} else {
|
|
list->cursor_row -= page;
|
|
list->start_row -= page;
|
|
}
|
|
tmp = data_get_row_n(list, list->cursor_row);
|
|
break;
|
|
case ML_CURSOR_PGDN:
|
|
if (list->cursor_row == list->nrows - 1) {
|
|
return;
|
|
}
|
|
unhighlight_current_row(list);
|
|
page = get_window_height(list);
|
|
if (page > list->nrows - list->cursor_row - 1) {
|
|
list->cursor_row = list->nrows - 1;
|
|
} else {
|
|
list->cursor_row += page;
|
|
list->start_row += page;
|
|
}
|
|
tmp = data_get_row_n(list, list->cursor_row);
|
|
break;
|
|
case ML_CURSOR_HOME:
|
|
if (list->cursor_row == 0) {
|
|
return;
|
|
}
|
|
unhighlight_current_row(list);
|
|
list->cursor_row = 0;
|
|
tmp = data_get_row_n(list, list->cursor_row);
|
|
break;
|
|
case ML_CURSOR_END:
|
|
if (list->cursor_row == list->nrows - 1) {
|
|
return;
|
|
}
|
|
unhighlight_current_row(list);
|
|
list->cursor_row = list->nrows - 1;
|
|
tmp = data_get_row_n(list, list->cursor_row);
|
|
break;
|
|
}
|
|
|
|
SMB_ASSERT(tmp);
|
|
list->current_row = tmp;
|
|
highlight_current_row(list);
|
|
fix_start_row(list);
|
|
}
|
|
|
|
const void *multilist_get_current_row(struct multilist *list)
|
|
{
|
|
return list->current_row;
|
|
}
|
|
|
|
void multilist_set_current_row(struct multilist *list, const void *row)
|
|
{
|
|
unsigned i;
|
|
const void *tmp;
|
|
|
|
for (i = 0, tmp = data_get_first_row(list);
|
|
tmp != NULL;
|
|
++i, tmp = data_get_next_row(list, tmp)) {
|
|
if (tmp == row) {
|
|
unhighlight_current_row(list);
|
|
list->cursor_row = i;
|
|
list->current_row = row;
|
|
highlight_current_row(list);
|
|
fix_start_row(list);
|
|
return;
|
|
}
|
|
}
|
|
}
|