📄 gnttree.c
字号:
#include "gntmarshal.h"#include "gntstyle.h"#include "gnttree.h"#include "gntutils.h"#include <string.h>#include <ctype.h>#define SEARCH_TIMEOUT 4000 /* 4 secs */#define SEARCHING(tree) (tree->search && tree->search->len > 0)enum{ SIG_SELECTION_CHANGED, SIG_SCROLLED, SIG_TOGGLED, SIG_COLLAPSED, SIGS,};#define TAB_SIZE 3/* XXX: Make this one into a GObject? * ... Probably not */struct _GntTreeRow{ void *key; void *data; /* XXX: unused */ gboolean collapsed; gboolean choice; /* Is this a choice-box? If choice is true, then child will be NULL */ gboolean isselected; GntTextFormatFlags flags; GntTreeRow *parent; GntTreeRow *child; GntTreeRow *next; GntTreeRow *prev; GList *columns; GntTree *tree;};struct _GntTreeCol{ char *text; int span; /* How many columns does it span? */};static void tree_selection_changed(GntTree *, GntTreeRow *, GntTreeRow *);static GntWidgetClass *parent_class = NULL;static guint signals[SIGS] = { 0 };/* Move the item at position old to position new */static GList *g_list_reposition_child(GList *list, int old, int new){ gpointer item = g_list_nth_data(list, old); list = g_list_remove(list, item); if (old < new) new--; /* because the positions would have shifted after removing the item */ list = g_list_insert(list, item, new); return list;}static GntTreeRow *_get_next(GntTreeRow *row, gboolean godeep){ if (row == NULL) return NULL; if (godeep && row->child) return row->child; if (row->next) return row->next; return _get_next(row->parent, FALSE);}static gbooleanrow_matches_search(GntTreeRow *row){ GntTree *t = row->tree; if (t->search && t->search->len > 0) { char *one = g_utf8_casefold(((GntTreeCol*)row->columns->data)->text, -1); char *two = g_utf8_casefold(t->search->str, -1); char *z = strstr(one, two); g_free(one); g_free(two); if (z == NULL) return FALSE; } return TRUE;}static GntTreeRow *get_next(GntTreeRow *row){ if (row == NULL) return NULL; while ((row = _get_next(row, !row->collapsed)) != NULL) { if (row_matches_search(row)) break; } return row;}/* Returns the n-th next row. If it doesn't exist, returns NULL */static GntTreeRow *get_next_n(GntTreeRow *row, int n){ while (row && n--) row = get_next(row); return row;}/* Returns the n-th next row. If it doesn't exist, then the last non-NULL node */static GntTreeRow *get_next_n_opt(GntTreeRow *row, int n, int *pos){ GntTreeRow *next = row; int r = 0; if (row == NULL) return NULL; while (row && n--) { row = get_next(row); if (row) { next = row; r++; } } if (pos) *pos = r; return next;}static GntTreeRow *get_last_child(GntTreeRow *row){ if (row == NULL) return NULL; if (!row->collapsed && row->child) row = row->child; else return row; while(row->next) row = row->next; return get_last_child(row);}static GntTreeRow *get_prev(GntTreeRow *row){ if (row == NULL) return NULL; while (row) { if (row->prev) row = get_last_child(row->prev); else row = row->parent; if (!row || row_matches_search(row)) break; } return row;}static GntTreeRow *get_prev_n(GntTreeRow *row, int n){ while (row && n--) row = get_prev(row); return row;}/* Distance of row from the root *//* XXX: This is uber-inefficient */static intget_root_distance(GntTreeRow *row){ if (row == NULL) return -1; return get_root_distance(get_prev(row)) + 1;}/* Returns the distance between a and b. * If a is 'above' b, then the distance is positive */static intget_distance(GntTreeRow *a, GntTreeRow *b){ /* First get the distance from a to the root. * Then the distance from b to the root. * Subtract. * It's not that good, but it works. */ int ha = get_root_distance(a); int hb = get_root_distance(b); return (hb - ha);}static intfind_depth(GntTreeRow *row){ int dep = -1; while (row) { dep++; row = row->parent; } return dep;}static char *update_row_text(GntTree *tree, GntTreeRow *row){ GString *string = g_string_new(NULL); GList *iter; int i; gboolean notfirst = FALSE; int lastvisible = tree->ncol; while (lastvisible && tree->columns[lastvisible].invisible) lastvisible--; for (i = 0, iter = row->columns; i < tree->ncol && iter; i++, iter = iter->next) { GntTreeCol *col = iter->data; const char *text; int len = gnt_util_onscreen_width(col->text, NULL); int fl = 0; gboolean cut = FALSE; int width; if (tree->columns[i].invisible) continue; if (i == lastvisible) width = GNT_WIDGET(tree)->priv.width - gnt_util_onscreen_width(string->str, NULL); else width = tree->columns[i].width; if (i == 0) { if (row->choice) { g_string_append_printf(string, "[%c] ", row->isselected ? 'X' : ' '); fl = 4; } else if (row->parent == NULL && row->child) { if (row->collapsed) { string = g_string_append(string, "+ "); } else { string = g_string_append(string, "- "); } fl = 2; } else { fl = TAB_SIZE * find_depth(row); g_string_append_printf(string, "%*s", fl, ""); } len += fl; } else if (notfirst) g_string_append_c(string, '|'); else g_string_append_c(string, ' '); notfirst = TRUE; if (len > width) { len = width - 1; cut = TRUE; } text = gnt_util_onscreen_width_to_pointer(col->text, len - fl, NULL); string = g_string_append_len(string, col->text, text - col->text); if (cut) { /* ellipsis */ if (gnt_ascii_only()) g_string_append_c(string, '~'); else string = g_string_append(string, "\342\200\246"); len++; } if (len < tree->columns[i].width && iter->next) g_string_append_printf(string, "%*s", width - len, ""); } return g_string_free(string, FALSE);}#define NEXT_X x += tree->columns[i].width + (i > 0 ? 1 : 0)static voidtree_mark_columns(GntTree *tree, int pos, int y, chtype type){ GntWidget *widget = GNT_WIDGET(tree); int i; int x = pos; gboolean notfirst = FALSE; for (i = 0; i < tree->ncol - 1; i++) { if (!tree->columns[i].invisible) { notfirst = TRUE; NEXT_X; } if (!tree->columns[i+1].invisible && notfirst) mvwaddch(widget->window, y, x, type); }}static voidredraw_tree(GntTree *tree){ int start, i; GntWidget *widget = GNT_WIDGET(tree); GntTreeRow *row; int pos, up, down; int rows, scrcol; if (!GNT_WIDGET_IS_FLAG_SET(GNT_WIDGET(tree), GNT_WIDGET_MAPPED)) return; if (GNT_WIDGET_IS_FLAG_SET(widget, GNT_WIDGET_NO_BORDER)) pos = 0; else pos = 1; if (tree->top == NULL) tree->top = tree->root; if (tree->current == NULL) { tree->current = tree->root; tree_selection_changed(tree, NULL, tree->current); } wbkgd(widget->window, COLOR_PAIR(GNT_COLOR_NORMAL)); start = 0; if (tree->show_title) { int i; int x = pos; mvwhline(widget->window, pos + 1, pos, ACS_HLINE | COLOR_PAIR(GNT_COLOR_NORMAL), widget->priv.width - pos - 1); mvwhline(widget->window, pos, pos, ' ' | COLOR_PAIR(GNT_COLOR_NORMAL), widget->priv.width - pos - 1); for (i = 0; i < tree->ncol; i++) { if (tree->columns[i].invisible) { continue; } mvwaddstr(widget->window, pos, x + 1, tree->columns[i].title); NEXT_X; } if (pos) { tree_mark_columns(tree, pos, 0, ACS_TTEE | COLOR_PAIR(GNT_COLOR_NORMAL)); tree_mark_columns(tree, pos, widget->priv.height - pos, ACS_BTEE | COLOR_PAIR(GNT_COLOR_NORMAL)); } tree_mark_columns(tree, pos, pos + 1, (tree->show_separator ? ACS_PLUS : ACS_HLINE) | COLOR_PAIR(GNT_COLOR_NORMAL)); tree_mark_columns(tree, pos, pos, (tree->show_separator ? ACS_VLINE : ' ') | COLOR_PAIR(GNT_COLOR_NORMAL)); start = 2; } rows = widget->priv.height - pos * 2 - start - 1; tree->bottom = get_next_n_opt(tree->top, rows, &down); if (down < rows) { tree->top = get_prev_n(tree->bottom, rows); if (tree->top == NULL) tree->top = tree->root; } up = get_distance(tree->top, tree->current); if (up < 0) tree->top = tree->current; else if (up >= widget->priv.height - pos) tree->top = get_prev_n(tree->current, rows); if (tree->top && !row_matches_search(tree->top)) tree->top = get_next(tree->top); row = tree->top; scrcol = widget->priv.width - 1 - 2 * pos; /* exclude the borders and the scrollbar */ for (i = start + pos; row && i < widget->priv.height - pos; i++, row = get_next(row)) { char *str; int wr; GntTextFormatFlags flags = row->flags; int attr = 0; if (!row_matches_search(row)) continue; str = update_row_text(tree, row); if ((wr = gnt_util_onscreen_width(str, NULL)) > scrcol) { char *s = (char*)gnt_util_onscreen_width_to_pointer(str, scrcol, &wr); *s = '\0'; } if (flags & GNT_TEXT_FLAG_BOLD) attr |= A_BOLD; if (flags & GNT_TEXT_FLAG_UNDERLINE) attr |= A_UNDERLINE; if (flags & GNT_TEXT_FLAG_BLINK) attr |= A_BLINK; if (row == tree->current) { if (gnt_widget_has_focus(widget)) attr |= COLOR_PAIR(GNT_COLOR_HIGHLIGHT); else attr |= COLOR_PAIR(GNT_COLOR_HIGHLIGHT_D); } else { if (flags & GNT_TEXT_FLAG_DIM) attr |= (A_DIM | COLOR_PAIR(GNT_COLOR_DISABLED)); else if (flags & GNT_TEXT_FLAG_HIGHLIGHT) attr |= (A_DIM | COLOR_PAIR(GNT_COLOR_HIGHLIGHT)); else attr |= COLOR_PAIR(GNT_COLOR_NORMAL); } wbkgdset(widget->window, '\0' | attr); mvwaddstr(widget->window, i, pos, str); whline(widget->window, ' ', scrcol - wr); tree->bottom = row; g_free(str); tree_mark_columns(tree, pos, i, (tree->show_separator ? ACS_VLINE : ' ') | attr); } wbkgdset(widget->window, '\0' | COLOR_PAIR(GNT_COLOR_NORMAL)); while (i < widget->priv.height - pos) { mvwhline(widget->window, i, pos, ' ', widget->priv.width - pos * 2 - 1); tree_mark_columns(tree, pos, i, (tree->show_separator ? ACS_VLINE : ' ')); i++; } scrcol = widget->priv.width - pos - 1; /* position of the scrollbar */ rows--; if (rows > 0) { int total; int showing, position; get_next_n_opt(tree->root, g_list_length(tree->list), &total); showing = rows * rows / MAX(total, 1) + 1; showing = MIN(rows, showing); total -= rows; up = get_distance(tree->root, tree->top); down = total - up; position = (rows - showing) * up / MAX(1, up + down); position = MAX((tree->top != tree->root), position); if (showing + position > rows) position = rows - showing; if (showing + position == rows && row) position = MAX(0, rows - 1 - showing); else if (showing + position < rows && !row) position = rows - showing; position += pos + start + 1; mvwvline(widget->window, pos + start + 1, scrcol, ' ' | COLOR_PAIR(GNT_COLOR_NORMAL), rows); mvwvline(widget->window, position, scrcol, ACS_CKBOARD | COLOR_PAIR(GNT_COLOR_HIGHLIGHT_D), showing); } mvwaddch(widget->window, start + pos, scrcol, ((tree->top != tree->root) ? ACS_UARROW : ' ') | COLOR_PAIR(GNT_COLOR_HIGHLIGHT_D)); mvwaddch(widget->window, widget->priv.height - pos - 1, scrcol, (row ? ACS_DARROW : ' ') | COLOR_PAIR(GNT_COLOR_HIGHLIGHT_D)); /* If there's a search-text, show it in the bottom of the tree */ if (tree->search && tree->search->len > 0) { const char *str = gnt_util_onscreen_width_to_pointer(tree->search->str, scrcol - 1, NULL); wbkgdset(widget->window, '\0' | COLOR_PAIR(GNT_COLOR_HIGHLIGHT_D)); mvwaddnstr(widget->window, widget->priv.height - pos - 1, pos, tree->search->str, str - tree->search->str); } gnt_widget_queue_update(widget);}static voidgnt_tree_draw(GntWidget *widget){ GntTree *tree = GNT_TREE(widget); redraw_tree(tree); GNTDEBUG;}static voidgnt_tree_size_request(GntWidget *widget){ if (widget->priv.height == 0) widget->priv.height = 10; /* XXX: Why?! */ if (widget->priv.width == 0)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -