| 1 /* |
|
| 2 * GNT - The GLib Ncurses Toolkit |
|
| 3 * |
|
| 4 * GNT is the legal property of its developers, whose names are too numerous |
|
| 5 * to list here. Please refer to the COPYRIGHT file distributed with this |
|
| 6 * source distribution. |
|
| 7 * |
|
| 8 * This library is free software; you can redistribute it and/or modify |
|
| 9 * it under the terms of the GNU General Public License as published by |
|
| 10 * the Free Software Foundation; either version 2 of the License, or |
|
| 11 * (at your option) any later version. |
|
| 12 * |
|
| 13 * This program is distributed in the hope that it will be useful, |
|
| 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
| 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
| 16 * GNU General Public License for more details. |
|
| 17 * |
|
| 18 * You should have received a copy of the GNU General Public License |
|
| 19 * along with this program; if not, write to the Free Software |
|
| 20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA |
|
| 21 */ |
|
| 22 |
|
| 23 #include "gntinternal.h" |
|
| 24 #include "gntstyle.h" |
|
| 25 #include "gnttree.h" |
|
| 26 #include "gntutils.h" |
|
| 27 |
|
| 28 #include <string.h> |
|
| 29 #include <ctype.h> |
|
| 30 |
|
| 31 #define SEARCH_TIMEOUT_S 4 /* 4 secs */ |
|
| 32 #define SEARCHING(tree) (tree->priv->search && tree->priv->search->len > 0) |
|
| 33 |
|
| 34 #define COLUMN_INVISIBLE(tree, index) (tree->columns[index].flags & GNT_TREE_COLUMN_INVISIBLE) |
|
| 35 #define BINARY_DATA(tree, index) (tree->columns[index].flags & GNT_TREE_COLUMN_BINARY_DATA) |
|
| 36 #define RIGHT_ALIGNED(tree, index) (tree->columns[index].flags & GNT_TREE_COLUMN_RIGHT_ALIGNED) |
|
| 37 |
|
| 38 enum |
|
| 39 { |
|
| 40 PROP_0, |
|
| 41 PROP_COLUMNS, |
|
| 42 PROP_EXPANDER, |
|
| 43 }; |
|
| 44 |
|
| 45 enum |
|
| 46 { |
|
| 47 SIG_SELECTION_CHANGED, |
|
| 48 SIG_SCROLLED, |
|
| 49 SIG_TOGGLED, |
|
| 50 SIG_COLLAPSED, |
|
| 51 SIGS, |
|
| 52 }; |
|
| 53 |
|
| 54 struct _GntTreePriv |
|
| 55 { |
|
| 56 GString *search; |
|
| 57 int search_timeout; |
|
| 58 int search_column; |
|
| 59 gboolean (*search_func)(GntTree *tree, gpointer key, const char *search, const char *current); |
|
| 60 |
|
| 61 GCompareFunc compare; |
|
| 62 int lastvisible; |
|
| 63 int expander_level; |
|
| 64 }; |
|
| 65 |
|
| 66 #define TAB_SIZE 3 |
|
| 67 |
|
| 68 /* XXX: Make this one into a GObject? |
|
| 69 * ... Probably not */ |
|
| 70 struct _GntTreeRow |
|
| 71 { |
|
| 72 int box_count; |
|
| 73 |
|
| 74 void *key; |
|
| 75 void *data; /* XXX: unused */ |
|
| 76 |
|
| 77 gboolean collapsed; |
|
| 78 gboolean choice; /* Is this a choice-box? |
|
| 79 If choice is true, then child will be NULL */ |
|
| 80 gboolean isselected; |
|
| 81 GntTextFormatFlags flags; |
|
| 82 int color; |
|
| 83 |
|
| 84 GntTreeRow *parent; |
|
| 85 GntTreeRow *child; |
|
| 86 GntTreeRow *next; |
|
| 87 GntTreeRow *prev; |
|
| 88 |
|
| 89 GList *columns; |
|
| 90 GntTree *tree; |
|
| 91 }; |
|
| 92 |
|
| 93 struct _GntTreeCol |
|
| 94 { |
|
| 95 char *text; |
|
| 96 gboolean isbinary; |
|
| 97 int span; /* How many columns does it span? */ |
|
| 98 }; |
|
| 99 |
|
| 100 static void tree_selection_changed(GntTree *, GntTreeRow *, GntTreeRow *); |
|
| 101 static void _gnt_tree_init_internals(GntTree *tree, int col); |
|
| 102 |
|
| 103 static GntWidgetClass *parent_class = NULL; |
|
| 104 static guint signals[SIGS] = { 0 }; |
|
| 105 |
|
| 106 static void |
|
| 107 readjust_columns(GntTree *tree) |
|
| 108 { |
|
| 109 int i, col, total; |
|
| 110 int width; |
|
| 111 #define WIDTH(i) (tree->columns[i].width_ratio ? tree->columns[i].width_ratio : tree->columns[i].width) |
|
| 112 gnt_widget_get_size(GNT_WIDGET(tree), &width, NULL); |
|
| 113 if (!GNT_WIDGET_IS_FLAG_SET(GNT_WIDGET(tree), GNT_WIDGET_NO_BORDER)) |
|
| 114 width -= 2; |
|
| 115 width -= 1; /* Exclude the scrollbar from the calculation */ |
|
| 116 for (i = 0, total = 0; i < tree->ncol ; i++) { |
|
| 117 if (tree->columns[i].flags & GNT_TREE_COLUMN_INVISIBLE) |
|
| 118 continue; |
|
| 119 if (tree->columns[i].flags & GNT_TREE_COLUMN_FIXED_SIZE) |
|
| 120 width -= WIDTH(i) + (tree->priv->lastvisible != i); |
|
| 121 else |
|
| 122 total += WIDTH(i) + (tree->priv->lastvisible != i); |
|
| 123 } |
|
| 124 |
|
| 125 if (total == 0) |
|
| 126 return; |
|
| 127 |
|
| 128 for (i = 0; i < tree->ncol; i++) { |
|
| 129 if (tree->columns[i].flags & GNT_TREE_COLUMN_INVISIBLE) |
|
| 130 continue; |
|
| 131 if (tree->columns[i].flags & GNT_TREE_COLUMN_FIXED_SIZE) |
|
| 132 col = WIDTH(i); |
|
| 133 else |
|
| 134 col = (WIDTH(i) * width) / total; |
|
| 135 gnt_tree_set_col_width(GNT_TREE(tree), i, col); |
|
| 136 } |
|
| 137 } |
|
| 138 |
|
| 139 /* Move the item at position old to position new */ |
|
| 140 static GList * |
|
| 141 g_list_reposition_child(GList *list, int old, int new) |
|
| 142 { |
|
| 143 gpointer item = g_list_nth_data(list, old); |
|
| 144 list = g_list_remove(list, item); |
|
| 145 if (old < new) |
|
| 146 new--; /* because the positions would have shifted after removing the item */ |
|
| 147 list = g_list_insert(list, item, new); |
|
| 148 return list; |
|
| 149 } |
|
| 150 |
|
| 151 static GntTreeRow * |
|
| 152 _get_next(GntTreeRow *row, gboolean godeep) |
|
| 153 { |
|
| 154 if (row == NULL) |
|
| 155 return NULL; |
|
| 156 if (godeep && row->child) |
|
| 157 return row->child; |
|
| 158 if (row->next) |
|
| 159 return row->next; |
|
| 160 return _get_next(row->parent, FALSE); |
|
| 161 } |
|
| 162 |
|
| 163 static gboolean |
|
| 164 row_matches_search(GntTreeRow *row) |
|
| 165 { |
|
| 166 GntTree *t = row->tree; |
|
| 167 if (t->priv->search && t->priv->search->len > 0) { |
|
| 168 GntTreeCol *col = (col = g_list_nth_data(row->columns, t->priv->search_column)) ? col : row->columns->data; |
|
| 169 char *one, *two, *z; |
|
| 170 if (t->priv->search_func) |
|
| 171 return t->priv->search_func(t, row->key, t->priv->search->str, col->text); |
|
| 172 one = g_utf8_casefold(col->text, -1); |
|
| 173 two = g_utf8_casefold(t->priv->search->str, -1); |
|
| 174 z = strstr(one, two); |
|
| 175 g_free(one); |
|
| 176 g_free(two); |
|
| 177 if (z == NULL) |
|
| 178 return FALSE; |
|
| 179 } |
|
| 180 return TRUE; |
|
| 181 } |
|
| 182 |
|
| 183 static GntTreeRow * |
|
| 184 get_next(GntTreeRow *row) |
|
| 185 { |
|
| 186 if (row == NULL) |
|
| 187 return NULL; |
|
| 188 while ((row = _get_next(row, !row->collapsed)) != NULL) { |
|
| 189 if (row_matches_search(row)) |
|
| 190 break; |
|
| 191 } |
|
| 192 return row; |
|
| 193 } |
|
| 194 |
|
| 195 /* Returns the n-th next row. If it doesn't exist, returns NULL */ |
|
| 196 static GntTreeRow * |
|
| 197 get_next_n(GntTreeRow *row, int n) |
|
| 198 { |
|
| 199 while (row && n--) |
|
| 200 row = get_next(row); |
|
| 201 return row; |
|
| 202 } |
|
| 203 |
|
| 204 /* Returns the n-th next row. If it doesn't exist, then the last non-NULL node */ |
|
| 205 static GntTreeRow * |
|
| 206 get_next_n_opt(GntTreeRow *row, int n, int *pos) |
|
| 207 { |
|
| 208 GntTreeRow *next = row; |
|
| 209 int r = 0; |
|
| 210 |
|
| 211 if (row == NULL) |
|
| 212 return NULL; |
|
| 213 |
|
| 214 while (row && n--) |
|
| 215 { |
|
| 216 row = get_next(row); |
|
| 217 if (row) |
|
| 218 { |
|
| 219 next = row; |
|
| 220 r++; |
|
| 221 } |
|
| 222 } |
|
| 223 |
|
| 224 if (pos) |
|
| 225 *pos = r; |
|
| 226 |
|
| 227 return next; |
|
| 228 } |
|
| 229 |
|
| 230 static GntTreeRow * |
|
| 231 get_last_child(GntTreeRow *row) |
|
| 232 { |
|
| 233 if (row == NULL) |
|
| 234 return NULL; |
|
| 235 if (!row->collapsed && row->child) |
|
| 236 row = row->child; |
|
| 237 else |
|
| 238 return row; |
|
| 239 |
|
| 240 while(row->next) |
|
| 241 row = row->next; |
|
| 242 return get_last_child(row); |
|
| 243 } |
|
| 244 |
|
| 245 static GntTreeRow * |
|
| 246 get_prev(GntTreeRow *row) |
|
| 247 { |
|
| 248 if (row == NULL) |
|
| 249 return NULL; |
|
| 250 while (row) { |
|
| 251 if (row->prev) |
|
| 252 row = get_last_child(row->prev); |
|
| 253 else |
|
| 254 row = row->parent; |
|
| 255 if (!row || row_matches_search(row)) |
|
| 256 break; |
|
| 257 } |
|
| 258 return row; |
|
| 259 } |
|
| 260 |
|
| 261 static GntTreeRow * |
|
| 262 get_prev_n(GntTreeRow *row, int n) |
|
| 263 { |
|
| 264 while (row && n--) |
|
| 265 row = get_prev(row); |
|
| 266 return row; |
|
| 267 } |
|
| 268 |
|
| 269 /* Distance of row from the root */ |
|
| 270 /* XXX: This is uber-inefficient */ |
|
| 271 static int |
|
| 272 get_root_distance(GntTreeRow *row) |
|
| 273 { |
|
| 274 if (row == NULL) |
|
| 275 return -1; |
|
| 276 return get_root_distance(get_prev(row)) + 1; |
|
| 277 } |
|
| 278 |
|
| 279 /* Returns the distance between a and b. |
|
| 280 * If a is 'above' b, then the distance is positive */ |
|
| 281 static int |
|
| 282 get_distance(GntTreeRow *a, GntTreeRow *b) |
|
| 283 { |
|
| 284 /* First get the distance from a to the root. |
|
| 285 * Then the distance from b to the root. |
|
| 286 * Subtract. |
|
| 287 * It's not that good, but it works. */ |
|
| 288 int ha = get_root_distance(a); |
|
| 289 int hb = get_root_distance(b); |
|
| 290 |
|
| 291 return (hb - ha); |
|
| 292 } |
|
| 293 |
|
| 294 static int |
|
| 295 find_depth(GntTreeRow *row) |
|
| 296 { |
|
| 297 int dep = -1; |
|
| 298 |
|
| 299 while (row) |
|
| 300 { |
|
| 301 dep++; |
|
| 302 row = row->parent; |
|
| 303 } |
|
| 304 |
|
| 305 return dep; |
|
| 306 } |
|
| 307 |
|
| 308 static char * |
|
| 309 update_row_text(GntTree *tree, GntTreeRow *row) |
|
| 310 { |
|
| 311 GString *string = g_string_new(NULL); |
|
| 312 GList *iter; |
|
| 313 int i; |
|
| 314 gboolean notfirst = FALSE; |
|
| 315 |
|
| 316 for (i = 0, iter = row->columns; i < tree->ncol && iter; i++, iter = iter->next) |
|
| 317 { |
|
| 318 GntTreeCol *col = iter->data; |
|
| 319 const char *text; |
|
| 320 int len; |
|
| 321 int fl = 0; |
|
| 322 gboolean cut = FALSE; |
|
| 323 int width; |
|
| 324 const char *display; |
|
| 325 |
|
| 326 if (COLUMN_INVISIBLE(tree, i)) |
|
| 327 continue; |
|
| 328 |
|
| 329 if (BINARY_DATA(tree, i)) |
|
| 330 display = ""; |
|
| 331 else |
|
| 332 display = col->text; |
|
| 333 |
|
| 334 len = gnt_util_onscreen_width(display, NULL); |
|
| 335 |
|
| 336 width = tree->columns[i].width; |
|
| 337 |
|
| 338 if (i == 0) |
|
| 339 { |
|
| 340 if (row->choice) |
|
| 341 { |
|
| 342 g_string_append_printf(string, "[%c] ", |
|
| 343 row->isselected ? 'X' : ' '); |
|
| 344 fl = 4; |
|
| 345 } |
|
| 346 else if (find_depth(row) < tree->priv->expander_level && row->child) |
|
| 347 { |
|
| 348 if (row->collapsed) |
|
| 349 { |
|
| 350 string = g_string_append(string, "+ "); |
|
| 351 } |
|
| 352 else |
|
| 353 { |
|
| 354 string = g_string_append(string, "- "); |
|
| 355 } |
|
| 356 fl = 2; |
|
| 357 } |
|
| 358 else |
|
| 359 { |
|
| 360 fl = TAB_SIZE * find_depth(row); |
|
| 361 g_string_append_printf(string, "%*s", fl, ""); |
|
| 362 } |
|
| 363 len += fl; |
|
| 364 } else if (notfirst && tree->show_separator) |
|
| 365 g_string_append_c(string, '|'); |
|
| 366 else |
|
| 367 g_string_append_c(string, ' '); |
|
| 368 |
|
| 369 notfirst = TRUE; |
|
| 370 |
|
| 371 if (len > width) { |
|
| 372 len = MAX(1, width - 1); |
|
| 373 cut = TRUE; |
|
| 374 } |
|
| 375 |
|
| 376 if (RIGHT_ALIGNED(tree, i) && len < tree->columns[i].width) { |
|
| 377 g_string_append_printf(string, "%*s", width - len - cut, ""); |
|
| 378 } |
|
| 379 |
|
| 380 text = gnt_util_onscreen_width_to_pointer(display, len - fl, NULL); |
|
| 381 string = g_string_append_len(string, display, text - display); |
|
| 382 if (cut && width > 1) { /* ellipsis */ |
|
| 383 if (gnt_ascii_only()) |
|
| 384 g_string_append_c(string, '~'); |
|
| 385 else |
|
| 386 string = g_string_append(string, "\342\200\246"); |
|
| 387 len++; |
|
| 388 } |
|
| 389 |
|
| 390 if (!RIGHT_ALIGNED(tree, i) && len < tree->columns[i].width && iter->next) |
|
| 391 g_string_append_printf(string, "%*s", width - len, ""); |
|
| 392 } |
|
| 393 return g_string_free(string, FALSE); |
|
| 394 } |
|
| 395 |
|
| 396 #define NEXT_X x += tree->columns[i].width + (i > 0 ? 1 : 0) |
|
| 397 |
|
| 398 static void |
|
| 399 tree_mark_columns(GntTree *tree, int pos, int y, chtype type) |
|
| 400 { |
|
| 401 GntWidget *widget = GNT_WIDGET(tree); |
|
| 402 int i; |
|
| 403 int x = pos; |
|
| 404 gboolean notfirst = FALSE; |
|
| 405 |
|
| 406 for (i = 0; i < tree->ncol - 1; i++) |
|
| 407 { |
|
| 408 if (!COLUMN_INVISIBLE(tree, i)) { |
|
| 409 notfirst = TRUE; |
|
| 410 NEXT_X; |
|
| 411 } |
|
| 412 if (!COLUMN_INVISIBLE(tree, i+1) && notfirst) |
|
| 413 mvwaddch(widget->window, y, x, type); |
|
| 414 } |
|
| 415 } |
|
| 416 |
|
| 417 static void |
|
| 418 redraw_tree(GntTree *tree) |
|
| 419 { |
|
| 420 int start, i; |
|
| 421 GntWidget *widget = GNT_WIDGET(tree); |
|
| 422 GntTreeRow *row; |
|
| 423 int pos, up, down = 0; |
|
| 424 int rows, scrcol; |
|
| 425 int current = 0; |
|
| 426 |
|
| 427 if (!GNT_WIDGET_IS_FLAG_SET(GNT_WIDGET(tree), GNT_WIDGET_MAPPED)) |
|
| 428 return; |
|
| 429 |
|
| 430 if (GNT_WIDGET_IS_FLAG_SET(widget, GNT_WIDGET_NO_BORDER)) |
|
| 431 pos = 0; |
|
| 432 else |
|
| 433 pos = 1; |
|
| 434 |
|
| 435 if (tree->top == NULL) |
|
| 436 tree->top = tree->root; |
|
| 437 if (tree->current == NULL && tree->root != NULL) { |
|
| 438 tree->current = tree->root; |
|
| 439 tree_selection_changed(tree, NULL, tree->current); |
|
| 440 } |
|
| 441 |
|
| 442 wbkgd(widget->window, gnt_color_pair(GNT_COLOR_NORMAL)); |
|
| 443 |
|
| 444 start = 0; |
|
| 445 if (tree->show_title) |
|
| 446 { |
|
| 447 int i; |
|
| 448 int x = pos; |
|
| 449 |
|
| 450 mvwhline(widget->window, pos + 1, pos, ACS_HLINE | gnt_color_pair(GNT_COLOR_NORMAL), |
|
| 451 widget->priv.width - pos - 1); |
|
| 452 mvwhline(widget->window, pos, pos, ' ' | gnt_color_pair(GNT_COLOR_NORMAL), |
|
| 453 widget->priv.width - pos - 1); |
|
| 454 |
|
| 455 for (i = 0; i < tree->ncol; i++) |
|
| 456 { |
|
| 457 if (COLUMN_INVISIBLE(tree, i)) { |
|
| 458 continue; |
|
| 459 } |
|
| 460 mvwaddnstr(widget->window, pos, x + (x != pos), tree->columns[i].title, tree->columns[i].width); |
|
| 461 NEXT_X; |
|
| 462 } |
|
| 463 if (pos) |
|
| 464 { |
|
| 465 tree_mark_columns(tree, pos, 0, |
|
| 466 (tree->show_separator ? ACS_TTEE : ACS_HLINE) | gnt_color_pair(GNT_COLOR_NORMAL)); |
|
| 467 tree_mark_columns(tree, pos, widget->priv.height - pos, |
|
| 468 (tree->show_separator ? ACS_BTEE : ACS_HLINE) | gnt_color_pair(GNT_COLOR_NORMAL)); |
|
| 469 } |
|
| 470 tree_mark_columns(tree, pos, pos + 1, |
|
| 471 (tree->show_separator ? ACS_PLUS : ACS_HLINE) | gnt_color_pair(GNT_COLOR_NORMAL)); |
|
| 472 tree_mark_columns(tree, pos, pos, |
|
| 473 (tree->show_separator ? ACS_VLINE : ' ') | gnt_color_pair(GNT_COLOR_NORMAL)); |
|
| 474 start = 2; |
|
| 475 } |
|
| 476 |
|
| 477 rows = widget->priv.height - pos * 2 - start - 1; |
|
| 478 tree->bottom = get_next_n_opt(tree->top, rows, &down); |
|
| 479 if (down < rows) |
|
| 480 { |
|
| 481 tree->top = get_prev_n(tree->bottom, rows); |
|
| 482 if (tree->top == NULL) |
|
| 483 tree->top = tree->root; |
|
| 484 } |
|
| 485 |
|
| 486 up = get_distance(tree->top, tree->current); |
|
| 487 if (up < 0) |
|
| 488 tree->top = tree->current; |
|
| 489 else if (up >= widget->priv.height - pos) |
|
| 490 tree->top = get_prev_n(tree->current, rows); |
|
| 491 |
|
| 492 if (tree->top && !row_matches_search(tree->top)) |
|
| 493 tree->top = get_next(tree->top); |
|
| 494 row = tree->top; |
|
| 495 scrcol = widget->priv.width - 1 - 2 * pos; /* exclude the borders and the scrollbar */ |
|
| 496 |
|
| 497 if (tree->current && !row_matches_search(tree->current)) { |
|
| 498 GntTreeRow *old = tree->current; |
|
| 499 tree->current = tree->top; |
|
| 500 tree_selection_changed(tree, old, tree->current); |
|
| 501 } |
|
| 502 |
|
| 503 for (i = start + pos; row && i < widget->priv.height - pos; |
|
| 504 i++, row = get_next(row)) |
|
| 505 { |
|
| 506 char *str; |
|
| 507 int wr; |
|
| 508 |
|
| 509 GntTextFormatFlags flags = row->flags; |
|
| 510 int attr = 0; |
|
| 511 |
|
| 512 if (!row_matches_search(row)) |
|
| 513 continue; |
|
| 514 str = update_row_text(tree, row); |
|
| 515 |
|
| 516 if ((wr = gnt_util_onscreen_width(str, NULL)) > scrcol) |
|
| 517 { |
|
| 518 char *s = (char*)gnt_util_onscreen_width_to_pointer(str, scrcol, &wr); |
|
| 519 *s = '\0'; |
|
| 520 } |
|
| 521 |
|
| 522 if (flags & GNT_TEXT_FLAG_BOLD) |
|
| 523 attr |= A_BOLD; |
|
| 524 if (flags & GNT_TEXT_FLAG_UNDERLINE) |
|
| 525 attr |= A_UNDERLINE; |
|
| 526 if (flags & GNT_TEXT_FLAG_BLINK) |
|
| 527 attr |= A_BLINK; |
|
| 528 |
|
| 529 if (row == tree->current) |
|
| 530 { |
|
| 531 current = i; |
|
| 532 attr |= A_BOLD; |
|
| 533 if (gnt_widget_has_focus(widget)) |
|
| 534 attr |= gnt_color_pair(GNT_COLOR_HIGHLIGHT); |
|
| 535 else |
|
| 536 attr |= gnt_color_pair(GNT_COLOR_HIGHLIGHT_D); |
|
| 537 } |
|
| 538 else |
|
| 539 { |
|
| 540 if (flags & GNT_TEXT_FLAG_DIM) |
|
| 541 if (row->color) |
|
| 542 attr |= (A_DIM | gnt_color_pair(row->color)); |
|
| 543 else |
|
| 544 attr |= (A_DIM | gnt_color_pair(GNT_COLOR_DISABLED)); |
|
| 545 else if (flags & GNT_TEXT_FLAG_HIGHLIGHT) |
|
| 546 attr |= (A_DIM | gnt_color_pair(GNT_COLOR_HIGHLIGHT)); |
|
| 547 else if (row->color) |
|
| 548 attr |= gnt_color_pair(row->color); |
|
| 549 else |
|
| 550 attr |= gnt_color_pair(GNT_COLOR_NORMAL); |
|
| 551 } |
|
| 552 |
|
| 553 wbkgdset(widget->window, '\0' | attr); |
|
| 554 mvwaddstr(widget->window, i, pos, C_(str)); |
|
| 555 whline(widget->window, ' ', scrcol - wr); |
|
| 556 tree->bottom = row; |
|
| 557 g_free(str); |
|
| 558 tree_mark_columns(tree, pos, i, |
|
| 559 (tree->show_separator ? ACS_VLINE : ' ') | attr); |
|
| 560 } |
|
| 561 |
|
| 562 wbkgdset(widget->window, '\0' | gnt_color_pair(GNT_COLOR_NORMAL)); |
|
| 563 while (i < widget->priv.height - pos) |
|
| 564 { |
|
| 565 mvwhline(widget->window, i, pos, ' ', |
|
| 566 widget->priv.width - pos * 2 - 1); |
|
| 567 tree_mark_columns(tree, pos, i, |
|
| 568 (tree->show_separator ? ACS_VLINE : ' ')); |
|
| 569 i++; |
|
| 570 } |
|
| 571 |
|
| 572 scrcol = widget->priv.width - pos - 1; /* position of the scrollbar */ |
|
| 573 rows--; |
|
| 574 if (rows > 0) |
|
| 575 { |
|
| 576 int total = 0; |
|
| 577 int showing, position; |
|
| 578 |
|
| 579 get_next_n_opt(tree->root, g_list_length(tree->list), &total); |
|
| 580 showing = rows * rows / MAX(total, 1) + 1; |
|
| 581 showing = MIN(rows, showing); |
|
| 582 |
|
| 583 total -= rows; |
|
| 584 up = get_distance(tree->root, tree->top); |
|
| 585 down = total - up; |
|
| 586 |
|
| 587 position = (rows - showing) * up / MAX(1, up + down); |
|
| 588 position = MAX((tree->top != tree->root), position); |
|
| 589 |
|
| 590 if (showing + position > rows) |
|
| 591 position = rows - showing; |
|
| 592 |
|
| 593 if (showing + position == rows && row) |
|
| 594 position = MAX(0, rows - 1 - showing); |
|
| 595 else if (showing + position < rows && !row) |
|
| 596 position = rows - showing; |
|
| 597 |
|
| 598 position += pos + start + 1; |
|
| 599 |
|
| 600 mvwvline(widget->window, pos + start + 1, scrcol, |
|
| 601 ' ' | gnt_color_pair(GNT_COLOR_NORMAL), rows); |
|
| 602 mvwvline(widget->window, position, scrcol, |
|
| 603 ACS_CKBOARD | gnt_color_pair(GNT_COLOR_HIGHLIGHT_D), showing); |
|
| 604 } |
|
| 605 |
|
| 606 mvwaddch(widget->window, start + pos, scrcol, |
|
| 607 ((tree->top != tree->root) ? ACS_UARROW : ' ') | |
|
| 608 gnt_color_pair(GNT_COLOR_HIGHLIGHT_D)); |
|
| 609 |
|
| 610 mvwaddch(widget->window, widget->priv.height - pos - 1, scrcol, |
|
| 611 (row ? ACS_DARROW : ' ') | gnt_color_pair(GNT_COLOR_HIGHLIGHT_D)); |
|
| 612 |
|
| 613 /* If there's a search-text, show it in the bottom of the tree */ |
|
| 614 if (tree->priv->search && tree->priv->search->len > 0) { |
|
| 615 const char *str = gnt_util_onscreen_width_to_pointer(tree->priv->search->str, scrcol - 1, NULL); |
|
| 616 wbkgdset(widget->window, '\0' | gnt_color_pair(GNT_COLOR_HIGHLIGHT_D)); |
|
| 617 mvwaddnstr(widget->window, widget->priv.height - pos - 1, pos, |
|
| 618 tree->priv->search->str, str - tree->priv->search->str); |
|
| 619 } |
|
| 620 wmove(widget->window, current, pos); |
|
| 621 |
|
| 622 gnt_widget_queue_update(widget); |
|
| 623 } |
|
| 624 |
|
| 625 static void |
|
| 626 gnt_tree_draw(GntWidget *widget) |
|
| 627 { |
|
| 628 GntTree *tree = GNT_TREE(widget); |
|
| 629 |
|
| 630 redraw_tree(tree); |
|
| 631 |
|
| 632 GNTDEBUG; |
|
| 633 } |
|
| 634 |
|
| 635 static void |
|
| 636 gnt_tree_size_request(GntWidget *widget) |
|
| 637 { |
|
| 638 if (widget->priv.height == 0) |
|
| 639 widget->priv.height = 10; /* XXX: Why?! */ |
|
| 640 if (widget->priv.width == 0) |
|
| 641 { |
|
| 642 GntTree *tree = GNT_TREE(widget); |
|
| 643 int i, width = 0; |
|
| 644 width = 1 + 2 * (!GNT_WIDGET_IS_FLAG_SET(GNT_WIDGET(tree), GNT_WIDGET_NO_BORDER)); |
|
| 645 for (i = 0; i < tree->ncol; i++) |
|
| 646 if (!COLUMN_INVISIBLE(tree, i)) { |
|
| 647 width = width + tree->columns[i].width; |
|
| 648 if (tree->priv->lastvisible != i) |
|
| 649 width++; |
|
| 650 } |
|
| 651 widget->priv.width = width; |
|
| 652 } |
|
| 653 } |
|
| 654 |
|
| 655 static void |
|
| 656 gnt_tree_map(GntWidget *widget) |
|
| 657 { |
|
| 658 GntTree *tree = GNT_TREE(widget); |
|
| 659 if (widget->priv.width == 0 || widget->priv.height == 0) |
|
| 660 { |
|
| 661 gnt_widget_size_request(widget); |
|
| 662 } |
|
| 663 tree->top = tree->root; |
|
| 664 tree->current = tree->root; |
|
| 665 GNTDEBUG; |
|
| 666 } |
|
| 667 |
|
| 668 static void |
|
| 669 tree_selection_changed(GntTree *tree, GntTreeRow *old, GntTreeRow *current) |
|
| 670 { |
|
| 671 g_signal_emit(tree, signals[SIG_SELECTION_CHANGED], 0, old ? old->key : NULL, |
|
| 672 current ? current->key : NULL); |
|
| 673 } |
|
| 674 |
|
| 675 static gboolean |
|
| 676 action_down(GntBindable *bind, GList *null) |
|
| 677 { |
|
| 678 int dist; |
|
| 679 GntTree *tree = GNT_TREE(bind); |
|
| 680 GntTreeRow *old = tree->current; |
|
| 681 GntTreeRow *row = get_next(tree->current); |
|
| 682 if (row == NULL) |
|
| 683 return FALSE; |
|
| 684 tree->current = row; |
|
| 685 if ((dist = get_distance(tree->current, tree->bottom)) < 0) |
|
| 686 gnt_tree_scroll(tree, -dist); |
|
| 687 else |
|
| 688 redraw_tree(tree); |
|
| 689 if (old != tree->current) |
|
| 690 tree_selection_changed(tree, old, tree->current); |
|
| 691 return TRUE; |
|
| 692 } |
|
| 693 |
|
| 694 static gboolean |
|
| 695 action_move_parent(GntBindable *bind, GList *null) |
|
| 696 { |
|
| 697 GntTree *tree = GNT_TREE(bind); |
|
| 698 GntTreeRow *row = tree->current; |
|
| 699 int dist; |
|
| 700 |
|
| 701 if (!row || !row->parent || SEARCHING(tree)) |
|
| 702 return FALSE; |
|
| 703 |
|
| 704 tree->current = row->parent; |
|
| 705 if ((dist = get_distance(tree->current, tree->top)) > 0) |
|
| 706 gnt_tree_scroll(tree, -dist); |
|
| 707 else |
|
| 708 redraw_tree(tree); |
|
| 709 tree_selection_changed(tree, row, tree->current); |
|
| 710 return TRUE; |
|
| 711 } |
|
| 712 |
|
| 713 static gboolean |
|
| 714 action_up(GntBindable *bind, GList *list) |
|
| 715 { |
|
| 716 int dist; |
|
| 717 GntTree *tree = GNT_TREE(bind); |
|
| 718 GntTreeRow *old = tree->current; |
|
| 719 GntTreeRow *row = get_prev(tree->current); |
|
| 720 if (!row) |
|
| 721 return FALSE; |
|
| 722 tree->current = row; |
|
| 723 if ((dist = get_distance(tree->current, tree->top)) > 0) |
|
| 724 gnt_tree_scroll(tree, -dist); |
|
| 725 else |
|
| 726 redraw_tree(tree); |
|
| 727 if (old != tree->current) |
|
| 728 tree_selection_changed(tree, old, tree->current); |
|
| 729 |
|
| 730 return TRUE; |
|
| 731 } |
|
| 732 |
|
| 733 static gboolean |
|
| 734 action_page_down(GntBindable *bind, GList *null) |
|
| 735 { |
|
| 736 GntTree *tree = GNT_TREE(bind); |
|
| 737 GntTreeRow *old = tree->current; |
|
| 738 GntTreeRow *row = get_next(tree->bottom); |
|
| 739 if (row) |
|
| 740 { |
|
| 741 int dist = get_distance(tree->top, tree->current); |
|
| 742 tree->top = tree->bottom; |
|
| 743 tree->current = get_next_n_opt(tree->top, dist, NULL); |
|
| 744 redraw_tree(tree); |
|
| 745 } |
|
| 746 else if (tree->current != tree->bottom) |
|
| 747 { |
|
| 748 tree->current = tree->bottom; |
|
| 749 redraw_tree(tree); |
|
| 750 } |
|
| 751 |
|
| 752 if (old != tree->current) |
|
| 753 tree_selection_changed(tree, old, tree->current); |
|
| 754 return TRUE; |
|
| 755 } |
|
| 756 |
|
| 757 static gboolean |
|
| 758 action_page_up(GntBindable *bind, GList *null) |
|
| 759 { |
|
| 760 GntWidget *widget = GNT_WIDGET(bind); |
|
| 761 GntTree *tree = GNT_TREE(bind); |
|
| 762 GntTreeRow *row; |
|
| 763 GntTreeRow *old = tree->current; |
|
| 764 |
|
| 765 if (tree->top != tree->root) |
|
| 766 { |
|
| 767 int dist = get_distance(tree->top, tree->current); |
|
| 768 row = get_prev_n(tree->top, widget->priv.height - 1 - |
|
| 769 tree->show_title * 2 - 2 * (GNT_WIDGET_IS_FLAG_SET(widget, GNT_WIDGET_NO_BORDER) == 0)); |
|
| 770 if (row == NULL) |
|
| 771 row = tree->root; |
|
| 772 tree->top = row; |
|
| 773 tree->current = get_next_n_opt(tree->top, dist, NULL); |
|
| 774 redraw_tree(tree); |
|
| 775 } |
|
| 776 else if (tree->current != tree->top) |
|
| 777 { |
|
| 778 tree->current = tree->top; |
|
| 779 redraw_tree(tree); |
|
| 780 } |
|
| 781 if (old != tree->current) |
|
| 782 tree_selection_changed(tree, old, tree->current); |
|
| 783 return TRUE; |
|
| 784 } |
|
| 785 |
|
| 786 static void |
|
| 787 end_search(GntTree *tree) |
|
| 788 { |
|
| 789 if (tree->priv->search) { |
|
| 790 g_source_remove(tree->priv->search_timeout); |
|
| 791 g_string_free(tree->priv->search, TRUE); |
|
| 792 tree->priv->search = NULL; |
|
| 793 tree->priv->search_timeout = 0; |
|
| 794 GNT_WIDGET_UNSET_FLAGS(GNT_WIDGET(tree), GNT_WIDGET_DISABLE_ACTIONS); |
|
| 795 } |
|
| 796 } |
|
| 797 |
|
| 798 static gboolean |
|
| 799 search_timeout(gpointer data) |
|
| 800 { |
|
| 801 GntTree *tree = data; |
|
| 802 |
|
| 803 end_search(tree); |
|
| 804 redraw_tree(tree); |
|
| 805 |
|
| 806 return FALSE; |
|
| 807 } |
|
| 808 |
|
| 809 static gboolean |
|
| 810 gnt_tree_key_pressed(GntWidget *widget, const char *text) |
|
| 811 { |
|
| 812 GntTree *tree = GNT_TREE(widget); |
|
| 813 GntTreeRow *old = tree->current; |
|
| 814 |
|
| 815 if (text[0] == '\r' || text[0] == '\n') { |
|
| 816 end_search(tree); |
|
| 817 gnt_widget_activate(widget); |
|
| 818 } else if (tree->priv->search) { |
|
| 819 gboolean changed = TRUE; |
|
| 820 if (g_unichar_isprint(*text)) { |
|
| 821 tree->priv->search = g_string_append_c(tree->priv->search, *text); |
|
| 822 } else if (g_utf8_collate(text, GNT_KEY_BACKSPACE) == 0) { |
|
| 823 if (tree->priv->search->len) |
|
| 824 tree->priv->search->str[--tree->priv->search->len] = '\0'; |
|
| 825 } else |
|
| 826 changed = FALSE; |
|
| 827 if (changed) { |
|
| 828 redraw_tree(tree); |
|
| 829 } else { |
|
| 830 gnt_bindable_perform_action_key(GNT_BINDABLE(tree), text); |
|
| 831 } |
|
| 832 g_source_remove(tree->priv->search_timeout); |
|
| 833 tree->priv->search_timeout = g_timeout_add_seconds(SEARCH_TIMEOUT_S, search_timeout, tree); |
|
| 834 return TRUE; |
|
| 835 } else if (text[0] == ' ' && text[1] == 0) { |
|
| 836 /* Space pressed */ |
|
| 837 GntTreeRow *row = tree->current; |
|
| 838 if (row && row->child) |
|
| 839 { |
|
| 840 row->collapsed = !row->collapsed; |
|
| 841 redraw_tree(tree); |
|
| 842 g_signal_emit(tree, signals[SIG_COLLAPSED], 0, row->key, row->collapsed); |
|
| 843 } |
|
| 844 else if (row && row->choice) |
|
| 845 { |
|
| 846 row->isselected = !row->isselected; |
|
| 847 g_signal_emit(tree, signals[SIG_TOGGLED], 0, row->key); |
|
| 848 redraw_tree(tree); |
|
| 849 } |
|
| 850 } else { |
|
| 851 return FALSE; |
|
| 852 } |
|
| 853 |
|
| 854 if (old != tree->current) |
|
| 855 { |
|
| 856 tree_selection_changed(tree, old, tree->current); |
|
| 857 } |
|
| 858 |
|
| 859 return TRUE; |
|
| 860 } |
|
| 861 |
|
| 862 static void |
|
| 863 gnt_tree_free_columns(GntTree *tree) |
|
| 864 { |
|
| 865 int i; |
|
| 866 for (i = 0; i < tree->ncol; i++) { |
|
| 867 g_free(tree->columns[i].title); |
|
| 868 } |
|
| 869 g_free(tree->columns); |
|
| 870 } |
|
| 871 |
|
| 872 static void |
|
| 873 gnt_tree_destroy(GntWidget *widget) |
|
| 874 { |
|
| 875 GntTree *tree = GNT_TREE(widget); |
|
| 876 |
|
| 877 end_search(tree); |
|
| 878 if (tree->hash) |
|
| 879 g_hash_table_destroy(tree->hash); |
|
| 880 g_list_free(tree->list); |
|
| 881 gnt_tree_free_columns(tree); |
|
| 882 g_free(tree->priv); |
|
| 883 } |
|
| 884 |
|
| 885 static gboolean |
|
| 886 gnt_tree_clicked(GntWidget *widget, GntMouseEvent event, int x, int y) |
|
| 887 { |
|
| 888 GntTree *tree = GNT_TREE(widget); |
|
| 889 GntTreeRow *old = tree->current; |
|
| 890 if (event == GNT_MOUSE_SCROLL_UP) { |
|
| 891 action_up(GNT_BINDABLE(widget), NULL); |
|
| 892 } else if (event == GNT_MOUSE_SCROLL_DOWN) { |
|
| 893 action_down(GNT_BINDABLE(widget), NULL); |
|
| 894 } else if (event == GNT_LEFT_MOUSE_DOWN) { |
|
| 895 GntTreeRow *row; |
|
| 896 GntTree *tree = GNT_TREE(widget); |
|
| 897 int pos = 1; |
|
| 898 if (GNT_WIDGET_IS_FLAG_SET(widget, GNT_WIDGET_NO_BORDER)) |
|
| 899 pos = 0; |
|
| 900 if (tree->show_title) |
|
| 901 pos += 2; |
|
| 902 pos = y - widget->priv.y - pos; |
|
| 903 row = get_next_n(tree->top, pos); |
|
| 904 if (row && tree->current != row) { |
|
| 905 GntTreeRow *old = tree->current; |
|
| 906 tree->current = row; |
|
| 907 redraw_tree(tree); |
|
| 908 tree_selection_changed(tree, old, tree->current); |
|
| 909 } else if (row && row == tree->current) { |
|
| 910 if (row->choice) { |
|
| 911 row->isselected = !row->isselected; |
|
| 912 g_signal_emit(tree, signals[SIG_TOGGLED], 0, row->key); |
|
| 913 redraw_tree(tree); |
|
| 914 } else { |
|
| 915 gnt_widget_activate(widget); |
|
| 916 } |
|
| 917 } |
|
| 918 } else { |
|
| 919 return FALSE; |
|
| 920 } |
|
| 921 if (old != tree->current) { |
|
| 922 tree_selection_changed(tree, old, tree->current); |
|
| 923 } |
|
| 924 return TRUE; |
|
| 925 } |
|
| 926 |
|
| 927 static void |
|
| 928 gnt_tree_size_changed(GntWidget *widget, int w, int h) |
|
| 929 { |
|
| 930 GntTree *tree = GNT_TREE(widget); |
|
| 931 if (widget->priv.width <= 0) |
|
| 932 return; |
|
| 933 |
|
| 934 readjust_columns(tree); |
|
| 935 } |
|
| 936 |
|
| 937 static gboolean |
|
| 938 start_search(GntBindable *bindable, GList *list) |
|
| 939 { |
|
| 940 GntTree *tree = GNT_TREE(bindable); |
|
| 941 if (tree->priv->search) |
|
| 942 return FALSE; |
|
| 943 GNT_WIDGET_SET_FLAGS(GNT_WIDGET(tree), GNT_WIDGET_DISABLE_ACTIONS); |
|
| 944 tree->priv->search = g_string_new(NULL); |
|
| 945 tree->priv->search_timeout = g_timeout_add_seconds(SEARCH_TIMEOUT_S, search_timeout, tree); |
|
| 946 return TRUE; |
|
| 947 } |
|
| 948 |
|
| 949 static gboolean |
|
| 950 end_search_action(GntBindable *bindable, GList *list) |
|
| 951 { |
|
| 952 GntTree *tree = GNT_TREE(bindable); |
|
| 953 if (tree->priv->search == NULL) |
|
| 954 return FALSE; |
|
| 955 GNT_WIDGET_UNSET_FLAGS(GNT_WIDGET(tree), GNT_WIDGET_DISABLE_ACTIONS); |
|
| 956 end_search(tree); |
|
| 957 redraw_tree(tree); |
|
| 958 return TRUE; |
|
| 959 } |
|
| 960 |
|
| 961 static gboolean |
|
| 962 move_first_action(GntBindable *bind, GList *null) |
|
| 963 { |
|
| 964 GntTree *tree = GNT_TREE(bind); |
|
| 965 GntTreeRow *row = tree->root; |
|
| 966 GntTreeRow *old = tree->current; |
|
| 967 if (row && !row_matches_search(row)) |
|
| 968 row = get_next(row); |
|
| 969 if (row) { |
|
| 970 tree->current = row; |
|
| 971 redraw_tree(tree); |
|
| 972 if (old != tree->current) |
|
| 973 tree_selection_changed(tree, old, tree->current); |
|
| 974 } |
|
| 975 |
|
| 976 return TRUE; |
|
| 977 } |
|
| 978 |
|
| 979 static gboolean |
|
| 980 move_last_action(GntBindable *bind, GList *null) |
|
| 981 { |
|
| 982 GntTree *tree = GNT_TREE(bind); |
|
| 983 GntTreeRow *old = tree->current; |
|
| 984 GntTreeRow *row = tree->bottom; |
|
| 985 GntTreeRow *next; |
|
| 986 |
|
| 987 while ((next = get_next(row))) |
|
| 988 row = next; |
|
| 989 |
|
| 990 if (row) { |
|
| 991 tree->current = row; |
|
| 992 redraw_tree(tree); |
|
| 993 if (old != tree->current) |
|
| 994 tree_selection_changed(tree, old, tree->current); |
|
| 995 } |
|
| 996 |
|
| 997 return TRUE; |
|
| 998 } |
|
| 999 |
|
| 1000 static void |
|
| 1001 gnt_tree_set_property(GObject *obj, guint prop_id, const GValue *value, |
|
| 1002 GParamSpec *spec) |
|
| 1003 { |
|
| 1004 GntTree *tree = GNT_TREE(obj); |
|
| 1005 switch (prop_id) { |
|
| 1006 case PROP_COLUMNS: |
|
| 1007 _gnt_tree_init_internals(tree, g_value_get_int(value)); |
|
| 1008 break; |
|
| 1009 case PROP_EXPANDER: |
|
| 1010 if (tree->priv->expander_level == g_value_get_int(value)) |
|
| 1011 break; |
|
| 1012 tree->priv->expander_level = g_value_get_int(value); |
|
| 1013 default: |
|
| 1014 break; |
|
| 1015 } |
|
| 1016 } |
|
| 1017 |
|
| 1018 static void |
|
| 1019 gnt_tree_get_property(GObject *obj, guint prop_id, GValue *value, |
|
| 1020 GParamSpec *spec) |
|
| 1021 { |
|
| 1022 GntTree *tree = GNT_TREE(obj); |
|
| 1023 switch (prop_id) { |
|
| 1024 case PROP_COLUMNS: |
|
| 1025 g_value_set_int(value, tree->ncol); |
|
| 1026 break; |
|
| 1027 case PROP_EXPANDER: |
|
| 1028 g_value_set_int(value, tree->priv->expander_level); |
|
| 1029 break; |
|
| 1030 default: |
|
| 1031 break; |
|
| 1032 } |
|
| 1033 } |
|
| 1034 |
|
| 1035 static void |
|
| 1036 gnt_tree_class_init(GntTreeClass *klass) |
|
| 1037 { |
|
| 1038 GntBindableClass *bindable = GNT_BINDABLE_CLASS(klass); |
|
| 1039 GObjectClass *gclass = G_OBJECT_CLASS(klass); |
|
| 1040 |
|
| 1041 parent_class = GNT_WIDGET_CLASS(klass); |
|
| 1042 parent_class->destroy = gnt_tree_destroy; |
|
| 1043 parent_class->draw = gnt_tree_draw; |
|
| 1044 parent_class->map = gnt_tree_map; |
|
| 1045 parent_class->size_request = gnt_tree_size_request; |
|
| 1046 parent_class->key_pressed = gnt_tree_key_pressed; |
|
| 1047 parent_class->clicked = gnt_tree_clicked; |
|
| 1048 parent_class->size_changed = gnt_tree_size_changed; |
|
| 1049 |
|
| 1050 gclass->set_property = gnt_tree_set_property; |
|
| 1051 gclass->get_property = gnt_tree_get_property; |
|
| 1052 g_object_class_install_property(gclass, |
|
| 1053 PROP_COLUMNS, |
|
| 1054 g_param_spec_int("columns", "Columns", |
|
| 1055 "Number of columns in the tree.", |
|
| 1056 1, G_MAXINT, 1, |
|
| 1057 G_PARAM_READWRITE|G_PARAM_STATIC_STRINGS |
|
| 1058 ) |
|
| 1059 ); |
|
| 1060 g_object_class_install_property(gclass, |
|
| 1061 PROP_EXPANDER, |
|
| 1062 g_param_spec_int("expander-level", "Expander level", |
|
| 1063 "Number of levels to show expander in the tree.", |
|
| 1064 0, G_MAXINT, 1, |
|
| 1065 G_PARAM_READWRITE|G_PARAM_STATIC_STRINGS |
|
| 1066 ) |
|
| 1067 ); |
|
| 1068 |
|
| 1069 signals[SIG_SELECTION_CHANGED] = |
|
| 1070 g_signal_new("selection-changed", |
|
| 1071 G_TYPE_FROM_CLASS(klass), |
|
| 1072 G_SIGNAL_RUN_LAST, |
|
| 1073 G_STRUCT_OFFSET(GntTreeClass, selection_changed), |
|
| 1074 NULL, NULL, NULL, |
|
| 1075 G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_POINTER); |
|
| 1076 signals[SIG_SCROLLED] = |
|
| 1077 g_signal_new("scrolled", |
|
| 1078 G_TYPE_FROM_CLASS(klass), |
|
| 1079 G_SIGNAL_RUN_LAST, |
|
| 1080 0, |
|
| 1081 NULL, NULL, NULL, |
|
| 1082 G_TYPE_NONE, 1, G_TYPE_INT); |
|
| 1083 signals[SIG_TOGGLED] = |
|
| 1084 g_signal_new("toggled", |
|
| 1085 G_TYPE_FROM_CLASS(klass), |
|
| 1086 G_SIGNAL_RUN_LAST, |
|
| 1087 G_STRUCT_OFFSET(GntTreeClass, toggled), |
|
| 1088 NULL, NULL, NULL, |
|
| 1089 G_TYPE_NONE, 1, G_TYPE_POINTER); |
|
| 1090 signals[SIG_COLLAPSED] = |
|
| 1091 g_signal_new("collapse-toggled", |
|
| 1092 G_TYPE_FROM_CLASS(klass), |
|
| 1093 G_SIGNAL_RUN_LAST, |
|
| 1094 0, |
|
| 1095 NULL, NULL, NULL, |
|
| 1096 G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_BOOLEAN); |
|
| 1097 |
|
| 1098 gnt_bindable_class_register_action(bindable, "move-up", action_up, |
|
| 1099 GNT_KEY_UP, NULL); |
|
| 1100 gnt_bindable_register_binding(bindable, "move-up", GNT_KEY_CTRL_P, NULL); |
|
| 1101 gnt_bindable_class_register_action(bindable, "move-down", action_down, |
|
| 1102 GNT_KEY_DOWN, NULL); |
|
| 1103 gnt_bindable_register_binding(bindable, "move-down", GNT_KEY_CTRL_N, NULL); |
|
| 1104 gnt_bindable_class_register_action(bindable, "move-parent", action_move_parent, |
|
| 1105 GNT_KEY_BACKSPACE, NULL); |
|
| 1106 gnt_bindable_class_register_action(bindable, "page-up", action_page_up, |
|
| 1107 GNT_KEY_PGUP, NULL); |
|
| 1108 gnt_bindable_class_register_action(bindable, "page-down", action_page_down, |
|
| 1109 GNT_KEY_PGDOWN, NULL); |
|
| 1110 gnt_bindable_class_register_action(bindable, "start-search", start_search, |
|
| 1111 "/", NULL); |
|
| 1112 gnt_bindable_class_register_action(bindable, "end-search", end_search_action, |
|
| 1113 "\033", NULL); |
|
| 1114 gnt_bindable_class_register_action(bindable, "move-first", move_first_action, |
|
| 1115 GNT_KEY_HOME, NULL); |
|
| 1116 gnt_bindable_class_register_action(bindable, "move-last", move_last_action, |
|
| 1117 GNT_KEY_END, NULL); |
|
| 1118 |
|
| 1119 gnt_style_read_actions(G_OBJECT_CLASS_TYPE(klass), bindable); |
|
| 1120 GNTDEBUG; |
|
| 1121 } |
|
| 1122 |
|
| 1123 static void |
|
| 1124 gnt_tree_init(GTypeInstance *instance, gpointer class) |
|
| 1125 { |
|
| 1126 GntWidget *widget = GNT_WIDGET(instance); |
|
| 1127 GntTree *tree = GNT_TREE(widget); |
|
| 1128 tree->show_separator = TRUE; |
|
| 1129 tree->priv = g_new0(GntTreePriv, 1); |
|
| 1130 GNT_WIDGET_SET_FLAGS(widget, GNT_WIDGET_GROW_X | GNT_WIDGET_GROW_Y | |
|
| 1131 GNT_WIDGET_CAN_TAKE_FOCUS | GNT_WIDGET_NO_SHADOW); |
|
| 1132 gnt_widget_set_take_focus(widget, TRUE); |
|
| 1133 widget->priv.minw = 4; |
|
| 1134 widget->priv.minh = 1; |
|
| 1135 GNTDEBUG; |
|
| 1136 } |
|
| 1137 |
|
| 1138 /****************************************************************************** |
|
| 1139 * GntTree API |
|
| 1140 *****************************************************************************/ |
|
| 1141 GType |
|
| 1142 gnt_tree_get_type(void) |
|
| 1143 { |
|
| 1144 static GType type = 0; |
|
| 1145 |
|
| 1146 if(type == 0) |
|
| 1147 { |
|
| 1148 static const GTypeInfo info = { |
|
| 1149 sizeof(GntTreeClass), |
|
| 1150 NULL, /* base_init */ |
|
| 1151 NULL, /* base_finalize */ |
|
| 1152 (GClassInitFunc)gnt_tree_class_init, |
|
| 1153 NULL, /* class_finalize */ |
|
| 1154 NULL, /* class_data */ |
|
| 1155 sizeof(GntTree), |
|
| 1156 0, /* n_preallocs */ |
|
| 1157 gnt_tree_init, /* instance_init */ |
|
| 1158 NULL /* value_table */ |
|
| 1159 }; |
|
| 1160 |
|
| 1161 type = g_type_register_static(GNT_TYPE_WIDGET, |
|
| 1162 "GntTree", |
|
| 1163 &info, 0); |
|
| 1164 } |
|
| 1165 |
|
| 1166 return type; |
|
| 1167 } |
|
| 1168 |
|
| 1169 static void |
|
| 1170 free_tree_col(gpointer data) |
|
| 1171 { |
|
| 1172 GntTreeCol *col = data; |
|
| 1173 if (!col->isbinary) |
|
| 1174 g_free(col->text); |
|
| 1175 g_free(col); |
|
| 1176 } |
|
| 1177 |
|
| 1178 static void |
|
| 1179 free_tree_row(gpointer data) |
|
| 1180 { |
|
| 1181 GntTreeRow *row = data; |
|
| 1182 |
|
| 1183 if (!row) |
|
| 1184 return; |
|
| 1185 |
|
| 1186 g_list_foreach(row->columns, (GFunc)free_tree_col, NULL); |
|
| 1187 g_list_free(row->columns); |
|
| 1188 g_free(row); |
|
| 1189 } |
|
| 1190 |
|
| 1191 GntWidget *gnt_tree_new() |
|
| 1192 { |
|
| 1193 return gnt_tree_new_with_columns(1); |
|
| 1194 } |
|
| 1195 |
|
| 1196 void gnt_tree_set_visible_rows(GntTree *tree, int rows) |
|
| 1197 { |
|
| 1198 GntWidget *widget = GNT_WIDGET(tree); |
|
| 1199 widget->priv.height = rows; |
|
| 1200 if (!GNT_WIDGET_IS_FLAG_SET(widget, GNT_WIDGET_NO_BORDER)) |
|
| 1201 widget->priv.height += 2; |
|
| 1202 } |
|
| 1203 |
|
| 1204 int gnt_tree_get_visible_rows(GntTree *tree) |
|
| 1205 { |
|
| 1206 GntWidget *widget = GNT_WIDGET(tree); |
|
| 1207 int ret = widget->priv.height; |
|
| 1208 if (!GNT_WIDGET_IS_FLAG_SET(widget, GNT_WIDGET_NO_BORDER)) |
|
| 1209 ret -= 2; |
|
| 1210 return ret; |
|
| 1211 } |
|
| 1212 |
|
| 1213 GList *gnt_tree_get_rows(GntTree *tree) |
|
| 1214 { |
|
| 1215 return tree->list; |
|
| 1216 } |
|
| 1217 |
|
| 1218 void gnt_tree_scroll(GntTree *tree, int count) |
|
| 1219 { |
|
| 1220 GntTreeRow *row; |
|
| 1221 |
|
| 1222 if (count < 0) |
|
| 1223 { |
|
| 1224 if (get_root_distance(tree->top) == 0) |
|
| 1225 return; |
|
| 1226 row = get_prev_n(tree->top, -count); |
|
| 1227 if (row == NULL) |
|
| 1228 row = tree->root; |
|
| 1229 tree->top = row; |
|
| 1230 } |
|
| 1231 else |
|
| 1232 { |
|
| 1233 get_next_n_opt(tree->bottom, count, &count); |
|
| 1234 tree->top = get_next_n(tree->top, count); |
|
| 1235 } |
|
| 1236 |
|
| 1237 redraw_tree(tree); |
|
| 1238 g_signal_emit(tree, signals[SIG_SCROLLED], 0, count); |
|
| 1239 } |
|
| 1240 |
|
| 1241 static gpointer |
|
| 1242 find_position(GntTree *tree, gpointer key, gpointer parent) |
|
| 1243 { |
|
| 1244 GntTreeRow *row; |
|
| 1245 |
|
| 1246 if (tree->priv->compare == NULL) |
|
| 1247 return NULL; |
|
| 1248 |
|
| 1249 if (parent == NULL) |
|
| 1250 row = tree->root; |
|
| 1251 else |
|
| 1252 row = g_hash_table_lookup(tree->hash, parent); |
|
| 1253 |
|
| 1254 if (!row) |
|
| 1255 return NULL; |
|
| 1256 |
|
| 1257 if (parent) |
|
| 1258 row = row->child; |
|
| 1259 |
|
| 1260 while (row) |
|
| 1261 { |
|
| 1262 if (tree->priv->compare(key, row->key) < 0) |
|
| 1263 return (row->prev ? row->prev->key : NULL); |
|
| 1264 if (row->next) |
|
| 1265 row = row->next; |
|
| 1266 else |
|
| 1267 return row->key; |
|
| 1268 } |
|
| 1269 return NULL; |
|
| 1270 } |
|
| 1271 |
|
| 1272 void gnt_tree_sort_row(GntTree *tree, gpointer key) |
|
| 1273 { |
|
| 1274 GntTreeRow *row, *q, *s; |
|
| 1275 int current, newp; |
|
| 1276 |
|
| 1277 if (!tree->priv->compare) |
|
| 1278 return; |
|
| 1279 |
|
| 1280 row = g_hash_table_lookup(tree->hash, key); |
|
| 1281 g_return_if_fail(row != NULL); |
|
| 1282 |
|
| 1283 current = g_list_index(tree->list, key); |
|
| 1284 |
|
| 1285 if (row->parent) |
|
| 1286 s = row->parent->child; |
|
| 1287 else |
|
| 1288 s = tree->root; |
|
| 1289 |
|
| 1290 q = NULL; |
|
| 1291 while (s) { |
|
| 1292 if (tree->priv->compare(row->key, s->key) < 0) |
|
| 1293 break; |
|
| 1294 q = s; |
|
| 1295 s = s->next; |
|
| 1296 } |
|
| 1297 |
|
| 1298 /* Move row between q and s */ |
|
| 1299 if (row == q || row == s) |
|
| 1300 return; |
|
| 1301 |
|
| 1302 if (q == NULL) { |
|
| 1303 /* row becomes the first child of its parent */ |
|
| 1304 row->prev->next = row->next; /* row->prev cannot be NULL at this point */ |
|
| 1305 if (row->next) |
|
| 1306 row->next->prev = row->prev; |
|
| 1307 if (row->parent) |
|
| 1308 row->parent->child = row; |
|
| 1309 else |
|
| 1310 tree->root = row; |
|
| 1311 row->next = s; |
|
| 1312 g_return_if_fail(s != NULL); /* s cannot be NULL */ |
|
| 1313 s->prev = row; |
|
| 1314 row->prev = NULL; |
|
| 1315 newp = g_list_index(tree->list, s) - 1; |
|
| 1316 } else { |
|
| 1317 if (row->prev) { |
|
| 1318 row->prev->next = row->next; |
|
| 1319 } else { |
|
| 1320 /* row was the first child of its parent */ |
|
| 1321 if (row->parent) |
|
| 1322 row->parent->child = row->next; |
|
| 1323 else |
|
| 1324 tree->top = row->next; |
|
| 1325 } |
|
| 1326 |
|
| 1327 if (row->next) |
|
| 1328 row->next->prev = row->prev; |
|
| 1329 |
|
| 1330 q->next = row; |
|
| 1331 row->prev = q; |
|
| 1332 if (s) |
|
| 1333 s->prev = row; |
|
| 1334 row->next = s; |
|
| 1335 newp = g_list_index(tree->list, q) + 1; |
|
| 1336 } |
|
| 1337 tree->list = g_list_reposition_child(tree->list, current, newp); |
|
| 1338 |
|
| 1339 redraw_tree(tree); |
|
| 1340 } |
|
| 1341 |
|
| 1342 GntTreeRow *gnt_tree_add_row_after(GntTree *tree, void *key, GntTreeRow *row, void *parent, void *bigbro) |
|
| 1343 { |
|
| 1344 GntTreeRow *pr = NULL; |
|
| 1345 |
|
| 1346 if (g_hash_table_lookup(tree->hash, key)) { |
|
| 1347 gnt_tree_remove(tree, key); |
|
| 1348 } |
|
| 1349 |
|
| 1350 row->tree = tree; |
|
| 1351 row->key = key; |
|
| 1352 row->data = NULL; |
|
| 1353 g_hash_table_replace(tree->hash, key, row); |
|
| 1354 |
|
| 1355 if (bigbro == NULL && tree->priv->compare) |
|
| 1356 { |
|
| 1357 bigbro = find_position(tree, key, parent); |
|
| 1358 } |
|
| 1359 |
|
| 1360 if (tree->root == NULL) |
|
| 1361 { |
|
| 1362 tree->root = row; |
|
| 1363 tree->list = g_list_prepend(tree->list, key); |
|
| 1364 } |
|
| 1365 else |
|
| 1366 { |
|
| 1367 int position = 0; |
|
| 1368 |
|
| 1369 if (bigbro) |
|
| 1370 { |
|
| 1371 pr = g_hash_table_lookup(tree->hash, bigbro); |
|
| 1372 if (pr) |
|
| 1373 { |
|
| 1374 if (pr->next) pr->next->prev = row; |
|
| 1375 row->next = pr->next; |
|
| 1376 row->prev = pr; |
|
| 1377 pr->next = row; |
|
| 1378 row->parent = pr->parent; |
|
| 1379 |
|
| 1380 position = g_list_index(tree->list, bigbro); |
|
| 1381 } |
|
| 1382 } |
|
| 1383 |
|
| 1384 if (pr == NULL && parent) |
|
| 1385 { |
|
| 1386 pr = g_hash_table_lookup(tree->hash, parent); |
|
| 1387 if (pr) |
|
| 1388 { |
|
| 1389 if (pr->child) pr->child->prev = row; |
|
| 1390 row->next = pr->child; |
|
| 1391 pr->child = row; |
|
| 1392 row->parent = pr; |
|
| 1393 |
|
| 1394 position = g_list_index(tree->list, parent); |
|
| 1395 } |
|
| 1396 } |
|
| 1397 |
|
| 1398 if (pr == NULL) |
|
| 1399 { |
|
| 1400 GntTreeRow *r = tree->root; |
|
| 1401 row->next = r; |
|
| 1402 if (r) r->prev = row; |
|
| 1403 if (tree->current == tree->root) |
|
| 1404 tree->current = row; |
|
| 1405 tree->root = row; |
|
| 1406 tree->list = g_list_prepend(tree->list, key); |
|
| 1407 } |
|
| 1408 else |
|
| 1409 { |
|
| 1410 tree->list = g_list_insert(tree->list, key, position + 1); |
|
| 1411 } |
|
| 1412 } |
|
| 1413 redraw_tree(tree); |
|
| 1414 |
|
| 1415 return row; |
|
| 1416 } |
|
| 1417 |
|
| 1418 GntTreeRow *gnt_tree_add_row_last(GntTree *tree, void *key, GntTreeRow *row, void *parent) |
|
| 1419 { |
|
| 1420 GntTreeRow *pr = NULL, *br = NULL; |
|
| 1421 |
|
| 1422 if (parent) |
|
| 1423 pr = g_hash_table_lookup(tree->hash, parent); |
|
| 1424 |
|
| 1425 if (pr) |
|
| 1426 br = pr->child; |
|
| 1427 else |
|
| 1428 br = tree->root; |
|
| 1429 |
|
| 1430 if (br) |
|
| 1431 { |
|
| 1432 while (br->next) |
|
| 1433 br = br->next; |
|
| 1434 } |
|
| 1435 |
|
| 1436 return gnt_tree_add_row_after(tree, key, row, parent, br ? br->key : NULL); |
|
| 1437 } |
|
| 1438 |
|
| 1439 gpointer gnt_tree_get_selection_data(GntTree *tree) |
|
| 1440 { |
|
| 1441 if (tree->current) |
|
| 1442 return tree->current->key; /* XXX: perhaps we should just get rid of 'data' */ |
|
| 1443 return NULL; |
|
| 1444 } |
|
| 1445 |
|
| 1446 char *gnt_tree_get_selection_text(GntTree *tree) |
|
| 1447 { |
|
| 1448 if (tree->current) |
|
| 1449 return update_row_text(tree, tree->current); |
|
| 1450 return NULL; |
|
| 1451 } |
|
| 1452 |
|
| 1453 GList *gnt_tree_get_row_text_list(GntTree *tree, gpointer key) |
|
| 1454 { |
|
| 1455 GList *list = NULL, *iter; |
|
| 1456 GntTreeRow *row = key ? g_hash_table_lookup(tree->hash, key) : tree->current; |
|
| 1457 int i; |
|
| 1458 |
|
| 1459 if (!row) |
|
| 1460 return NULL; |
|
| 1461 |
|
| 1462 for (i = 0, iter = row->columns; i < tree->ncol && iter; |
|
| 1463 i++, iter = iter->next) |
|
| 1464 { |
|
| 1465 GntTreeCol *col = iter->data; |
|
| 1466 list = g_list_append(list, BINARY_DATA(tree, i) ? col->text : g_strdup(col->text)); |
|
| 1467 } |
|
| 1468 |
|
| 1469 return list; |
|
| 1470 } |
|
| 1471 |
|
| 1472 GList *gnt_tree_get_selection_text_list(GntTree *tree) |
|
| 1473 { |
|
| 1474 return gnt_tree_get_row_text_list(tree, NULL); |
|
| 1475 } |
|
| 1476 |
|
| 1477 void gnt_tree_remove(GntTree *tree, gpointer key) |
|
| 1478 { |
|
| 1479 GntTreeRow *row = g_hash_table_lookup(tree->hash, key); |
|
| 1480 static int depth = 0; /* Only redraw after all child nodes are removed */ |
|
| 1481 if (row) |
|
| 1482 { |
|
| 1483 gboolean redraw = FALSE; |
|
| 1484 |
|
| 1485 if (row->child) { |
|
| 1486 depth++; |
|
| 1487 while (row->child) { |
|
| 1488 gnt_tree_remove(tree, row->child->key); |
|
| 1489 } |
|
| 1490 depth--; |
|
| 1491 } |
|
| 1492 |
|
| 1493 if (get_distance(tree->top, row) >= 0 && get_distance(row, tree->bottom) >= 0) |
|
| 1494 redraw = TRUE; |
|
| 1495 |
|
| 1496 /* Update root/top/current/bottom if necessary */ |
|
| 1497 if (tree->root == row) |
|
| 1498 tree->root = get_next(row); |
|
| 1499 if (tree->top == row) |
|
| 1500 { |
|
| 1501 if (tree->top != tree->root) |
|
| 1502 tree->top = get_prev(row); |
|
| 1503 else |
|
| 1504 tree->top = get_next(row); |
|
| 1505 } |
|
| 1506 if (tree->current == row) |
|
| 1507 { |
|
| 1508 if (tree->current != tree->root) |
|
| 1509 tree->current = get_prev(row); |
|
| 1510 else |
|
| 1511 tree->current = get_next(row); |
|
| 1512 tree_selection_changed(tree, row, tree->current); |
|
| 1513 } |
|
| 1514 if (tree->bottom == row) |
|
| 1515 { |
|
| 1516 tree->bottom = get_prev(row); |
|
| 1517 } |
|
| 1518 |
|
| 1519 /* Fix the links */ |
|
| 1520 if (row->next) |
|
| 1521 row->next->prev = row->prev; |
|
| 1522 if (row->parent && row->parent->child == row) |
|
| 1523 row->parent->child = row->next; |
|
| 1524 if (row->prev) |
|
| 1525 row->prev->next = row->next; |
|
| 1526 |
|
| 1527 g_hash_table_remove(tree->hash, key); |
|
| 1528 tree->list = g_list_remove(tree->list, key); |
|
| 1529 |
|
| 1530 if (redraw && depth == 0) |
|
| 1531 { |
|
| 1532 redraw_tree(tree); |
|
| 1533 } |
|
| 1534 } |
|
| 1535 } |
|
| 1536 |
|
| 1537 static gboolean |
|
| 1538 return_true(gpointer key, gpointer data, gpointer null) |
|
| 1539 { |
|
| 1540 return TRUE; |
|
| 1541 } |
|
| 1542 |
|
| 1543 void gnt_tree_remove_all(GntTree *tree) |
|
| 1544 { |
|
| 1545 tree->root = NULL; |
|
| 1546 g_hash_table_foreach_remove(tree->hash, (GHRFunc)return_true, tree); |
|
| 1547 g_list_free(tree->list); |
|
| 1548 tree->list = NULL; |
|
| 1549 tree->current = tree->top = tree->bottom = NULL; |
|
| 1550 } |
|
| 1551 |
|
| 1552 int gnt_tree_get_selection_visible_line(GntTree *tree) |
|
| 1553 { |
|
| 1554 return get_distance(tree->top, tree->current) + |
|
| 1555 !!(GNT_WIDGET_IS_FLAG_SET(GNT_WIDGET(tree), GNT_WIDGET_NO_BORDER)); |
|
| 1556 } |
|
| 1557 |
|
| 1558 void gnt_tree_change_text(GntTree *tree, gpointer key, int colno, const char *text) |
|
| 1559 { |
|
| 1560 GntTreeRow *row; |
|
| 1561 GntTreeCol *col; |
|
| 1562 |
|
| 1563 g_return_if_fail(colno < tree->ncol); |
|
| 1564 |
|
| 1565 row = g_hash_table_lookup(tree->hash, key); |
|
| 1566 if (row) |
|
| 1567 { |
|
| 1568 col = g_list_nth_data(row->columns, colno); |
|
| 1569 if (BINARY_DATA(tree, colno)) { |
|
| 1570 col->text = (gpointer)text; |
|
| 1571 } else { |
|
| 1572 g_free(col->text); |
|
| 1573 col->text = g_strdup(text ? text : ""); |
|
| 1574 } |
|
| 1575 |
|
| 1576 if (GNT_WIDGET_IS_FLAG_SET(GNT_WIDGET(tree), GNT_WIDGET_MAPPED) && |
|
| 1577 get_distance(tree->top, row) >= 0 && get_distance(row, tree->bottom) >= 0) |
|
| 1578 redraw_tree(tree); |
|
| 1579 } |
|
| 1580 } |
|
| 1581 |
|
| 1582 GntTreeRow *gnt_tree_add_choice(GntTree *tree, void *key, GntTreeRow *row, void *parent, void *bigbro) |
|
| 1583 { |
|
| 1584 GntTreeRow *r; |
|
| 1585 r = g_hash_table_lookup(tree->hash, key); |
|
| 1586 g_return_val_if_fail(!r || !r->choice, NULL); |
|
| 1587 |
|
| 1588 if (bigbro == NULL) { |
|
| 1589 if (tree->priv->compare) |
|
| 1590 bigbro = find_position(tree, key, parent); |
|
| 1591 else { |
|
| 1592 r = g_hash_table_lookup(tree->hash, parent); |
|
| 1593 if (!r) |
|
| 1594 r = tree->root; |
|
| 1595 else |
|
| 1596 r = r->child; |
|
| 1597 if (r) { |
|
| 1598 while (r->next) |
|
| 1599 r = r->next; |
|
| 1600 bigbro = r->key; |
|
| 1601 } |
|
| 1602 } |
|
| 1603 } |
|
| 1604 row = gnt_tree_add_row_after(tree, key, row, parent, bigbro); |
|
| 1605 row->choice = TRUE; |
|
| 1606 |
|
| 1607 return row; |
|
| 1608 } |
|
| 1609 |
|
| 1610 void gnt_tree_set_choice(GntTree *tree, void *key, gboolean set) |
|
| 1611 { |
|
| 1612 GntTreeRow *row = g_hash_table_lookup(tree->hash, key); |
|
| 1613 |
|
| 1614 if (!row) |
|
| 1615 return; |
|
| 1616 g_return_if_fail(row->choice); |
|
| 1617 |
|
| 1618 row->isselected = set; |
|
| 1619 redraw_tree(tree); |
|
| 1620 } |
|
| 1621 |
|
| 1622 gboolean gnt_tree_get_choice(GntTree *tree, void *key) |
|
| 1623 { |
|
| 1624 GntTreeRow *row = g_hash_table_lookup(tree->hash, key); |
|
| 1625 |
|
| 1626 if (!row) |
|
| 1627 return FALSE; |
|
| 1628 g_return_val_if_fail(row->choice, FALSE); |
|
| 1629 |
|
| 1630 return row->isselected; |
|
| 1631 } |
|
| 1632 |
|
| 1633 void gnt_tree_set_row_flags(GntTree *tree, void *key, GntTextFormatFlags flags) |
|
| 1634 { |
|
| 1635 GntTreeRow *row = g_hash_table_lookup(tree->hash, key); |
|
| 1636 if (!row || row->flags == flags) |
|
| 1637 return; |
|
| 1638 |
|
| 1639 row->flags = flags; |
|
| 1640 redraw_tree(tree); /* XXX: It shouldn't be necessary to redraw the whole darned tree */ |
|
| 1641 } |
|
| 1642 |
|
| 1643 void gnt_tree_set_row_color(GntTree *tree, void *key, int color) |
|
| 1644 { |
|
| 1645 GntTreeRow *row = g_hash_table_lookup(tree->hash, key); |
|
| 1646 if (!row || row->color == color) |
|
| 1647 return; |
|
| 1648 |
|
| 1649 row->color = color; |
|
| 1650 redraw_tree(tree); |
|
| 1651 } |
|
| 1652 |
|
| 1653 void gnt_tree_set_selected(GntTree *tree , void *key) |
|
| 1654 { |
|
| 1655 int dist; |
|
| 1656 GntTreeRow *row = g_hash_table_lookup(tree->hash, key); |
|
| 1657 if (!row || row == tree->current) |
|
| 1658 return; |
|
| 1659 |
|
| 1660 if (tree->top == NULL) |
|
| 1661 tree->top = row; |
|
| 1662 if (tree->bottom == NULL) |
|
| 1663 tree->bottom = row; |
|
| 1664 |
|
| 1665 tree->current = row; |
|
| 1666 if ((dist = get_distance(tree->current, tree->bottom)) < 0) |
|
| 1667 gnt_tree_scroll(tree, -dist); |
|
| 1668 else if ((dist = get_distance(tree->current, tree->top)) > 0) |
|
| 1669 gnt_tree_scroll(tree, -dist); |
|
| 1670 else |
|
| 1671 redraw_tree(tree); |
|
| 1672 tree_selection_changed(tree, row, tree->current); |
|
| 1673 } |
|
| 1674 |
|
| 1675 static void _gnt_tree_init_internals(GntTree *tree, int col) |
|
| 1676 { |
|
| 1677 gnt_tree_free_columns(tree); |
|
| 1678 |
|
| 1679 tree->ncol = col; |
|
| 1680 tree->hash = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, free_tree_row); |
|
| 1681 tree->columns = g_new0(struct _GntTreeColInfo, col); |
|
| 1682 tree->priv->lastvisible = col - 1; |
|
| 1683 while (col--) |
|
| 1684 { |
|
| 1685 tree->columns[col].width = 15; |
|
| 1686 } |
|
| 1687 tree->list = NULL; |
|
| 1688 tree->show_title = FALSE; |
|
| 1689 g_object_notify(G_OBJECT(tree), "columns"); |
|
| 1690 } |
|
| 1691 |
|
| 1692 GntWidget *gnt_tree_new_with_columns(int col) |
|
| 1693 { |
|
| 1694 GntWidget *widget = g_object_new(GNT_TYPE_TREE, |
|
| 1695 "columns", col, |
|
| 1696 "expander-level", 1, |
|
| 1697 NULL); |
|
| 1698 |
|
| 1699 return widget; |
|
| 1700 } |
|
| 1701 |
|
| 1702 GntTreeRow *gnt_tree_create_row_from_list(GntTree *tree, GList *list) |
|
| 1703 { |
|
| 1704 GList *iter; |
|
| 1705 int i; |
|
| 1706 GntTreeRow *row = g_new0(GntTreeRow, 1); |
|
| 1707 |
|
| 1708 for (i = 0, iter = list; i < tree->ncol && iter; iter = iter->next, i++) |
|
| 1709 { |
|
| 1710 GntTreeCol *col = g_new0(GntTreeCol, 1); |
|
| 1711 col->span = 1; |
|
| 1712 if (BINARY_DATA(tree, i)) { |
|
| 1713 col->text = iter->data; |
|
| 1714 col->isbinary = TRUE; |
|
| 1715 } else { |
|
| 1716 col->text = g_strdup(iter->data ? iter->data : ""); |
|
| 1717 col->isbinary = FALSE; |
|
| 1718 } |
|
| 1719 |
|
| 1720 row->columns = g_list_append(row->columns, col); |
|
| 1721 } |
|
| 1722 |
|
| 1723 return row; |
|
| 1724 } |
|
| 1725 |
|
| 1726 GntTreeRow *gnt_tree_create_row(GntTree *tree, ...) |
|
| 1727 { |
|
| 1728 int i; |
|
| 1729 va_list args; |
|
| 1730 GList *list = NULL; |
|
| 1731 GntTreeRow *row; |
|
| 1732 |
|
| 1733 va_start(args, tree); |
|
| 1734 for (i = 0; i < tree->ncol; i++) |
|
| 1735 { |
|
| 1736 list = g_list_append(list, va_arg(args, char *)); |
|
| 1737 } |
|
| 1738 va_end(args); |
|
| 1739 |
|
| 1740 row = gnt_tree_create_row_from_list(tree, list); |
|
| 1741 g_list_free(list); |
|
| 1742 |
|
| 1743 return row; |
|
| 1744 } |
|
| 1745 |
|
| 1746 void gnt_tree_set_col_width(GntTree *tree, int col, int width) |
|
| 1747 { |
|
| 1748 g_return_if_fail(col < tree->ncol); |
|
| 1749 |
|
| 1750 tree->columns[col].width = width; |
|
| 1751 if (tree->columns[col].width_ratio == 0) |
|
| 1752 tree->columns[col].width_ratio = width; |
|
| 1753 } |
|
| 1754 |
|
| 1755 void gnt_tree_set_column_title(GntTree *tree, int index, const char *title) |
|
| 1756 { |
|
| 1757 g_free(tree->columns[index].title); |
|
| 1758 tree->columns[index].title = g_strdup(title); |
|
| 1759 } |
|
| 1760 |
|
| 1761 void gnt_tree_set_column_titles(GntTree *tree, ...) |
|
| 1762 { |
|
| 1763 int i; |
|
| 1764 va_list args; |
|
| 1765 |
|
| 1766 va_start(args, tree); |
|
| 1767 for (i = 0; i < tree->ncol; i++) |
|
| 1768 { |
|
| 1769 const char *title = va_arg(args, const char *); |
|
| 1770 tree->columns[i].title = g_strdup(title); |
|
| 1771 } |
|
| 1772 va_end(args); |
|
| 1773 } |
|
| 1774 |
|
| 1775 void gnt_tree_set_show_title(GntTree *tree, gboolean set) |
|
| 1776 { |
|
| 1777 tree->show_title = set; |
|
| 1778 GNT_WIDGET(tree)->priv.minh = (set ? 6 : 4); |
|
| 1779 } |
|
| 1780 |
|
| 1781 void gnt_tree_set_compare_func(GntTree *tree, GCompareFunc func) |
|
| 1782 { |
|
| 1783 tree->priv->compare = func; |
|
| 1784 } |
|
| 1785 |
|
| 1786 void gnt_tree_set_expanded(GntTree *tree, void *key, gboolean expanded) |
|
| 1787 { |
|
| 1788 GntTreeRow *row = g_hash_table_lookup(tree->hash, key); |
|
| 1789 if (row) { |
|
| 1790 row->collapsed = !expanded; |
|
| 1791 if (GNT_WIDGET(tree)->window) |
|
| 1792 gnt_widget_draw(GNT_WIDGET(tree)); |
|
| 1793 g_signal_emit(tree, signals[SIG_COLLAPSED], 0, key, row->collapsed); |
|
| 1794 } |
|
| 1795 } |
|
| 1796 |
|
| 1797 void gnt_tree_set_show_separator(GntTree *tree, gboolean set) |
|
| 1798 { |
|
| 1799 tree->show_separator = set; |
|
| 1800 } |
|
| 1801 |
|
| 1802 void gnt_tree_adjust_columns(GntTree *tree) |
|
| 1803 { |
|
| 1804 GntTreeRow *row = tree->root; |
|
| 1805 int *widths, i, twidth; |
|
| 1806 |
|
| 1807 widths = g_new0(int, tree->ncol); |
|
| 1808 while (row) { |
|
| 1809 GList *iter; |
|
| 1810 for (i = 0, iter = row->columns; iter; iter = iter->next, i++) { |
|
| 1811 GntTreeCol *col = iter->data; |
|
| 1812 int w = gnt_util_onscreen_width(col->text, NULL); |
|
| 1813 if (i == 0 && row->choice) |
|
| 1814 w += 4; |
|
| 1815 if (i == 0) { |
|
| 1816 w += find_depth(row) * TAB_SIZE; |
|
| 1817 } |
|
| 1818 if (widths[i] < w) |
|
| 1819 widths[i] = w; |
|
| 1820 } |
|
| 1821 row = get_next(row); |
|
| 1822 } |
|
| 1823 |
|
| 1824 twidth = 1 + 2 * (!GNT_WIDGET_IS_FLAG_SET(GNT_WIDGET(tree), GNT_WIDGET_NO_BORDER)); |
|
| 1825 for (i = 0; i < tree->ncol; i++) { |
|
| 1826 if (tree->columns[i].flags & GNT_TREE_COLUMN_FIXED_SIZE) |
|
| 1827 widths[i] = tree->columns[i].width; |
|
| 1828 gnt_tree_set_col_width(tree, i, widths[i]); |
|
| 1829 if (!COLUMN_INVISIBLE(tree, i)) { |
|
| 1830 twidth = twidth + widths[i]; |
|
| 1831 if (tree->priv->lastvisible != i) |
|
| 1832 twidth += 1; |
|
| 1833 } |
|
| 1834 } |
|
| 1835 g_free(widths); |
|
| 1836 |
|
| 1837 gnt_widget_set_size(GNT_WIDGET(tree), twidth, -1); |
|
| 1838 } |
|
| 1839 |
|
| 1840 void gnt_tree_set_hash_fns(GntTree *tree, gpointer hash, gpointer eq, gpointer kd) |
|
| 1841 { |
|
| 1842 g_hash_table_foreach_remove(tree->hash, return_true, NULL); |
|
| 1843 g_hash_table_destroy(tree->hash); |
|
| 1844 tree->hash = g_hash_table_new_full(hash, eq, kd, free_tree_row); |
|
| 1845 } |
|
| 1846 |
|
| 1847 static void |
|
| 1848 set_column_flag(GntTree *tree, int col, GntTreeColumnFlag flag, gboolean set) |
|
| 1849 { |
|
| 1850 if (set) |
|
| 1851 tree->columns[col].flags |= flag; |
|
| 1852 else |
|
| 1853 tree->columns[col].flags &= ~flag; |
|
| 1854 } |
|
| 1855 |
|
| 1856 void gnt_tree_set_column_visible(GntTree *tree, int col, gboolean vis) |
|
| 1857 { |
|
| 1858 g_return_if_fail(col < tree->ncol); |
|
| 1859 set_column_flag(tree, col, GNT_TREE_COLUMN_INVISIBLE, !vis); |
|
| 1860 if (vis) { |
|
| 1861 /* the column is visible */ |
|
| 1862 if (tree->priv->lastvisible < col) |
|
| 1863 tree->priv->lastvisible = col; |
|
| 1864 } else { |
|
| 1865 if (tree->priv->lastvisible == col) |
|
| 1866 while (tree->priv->lastvisible) { |
|
| 1867 tree->priv->lastvisible--; |
|
| 1868 if (!COLUMN_INVISIBLE(tree, tree->priv->lastvisible)) |
|
| 1869 break; |
|
| 1870 } |
|
| 1871 } |
|
| 1872 if (GNT_WIDGET_IS_FLAG_SET(GNT_WIDGET(tree), GNT_WIDGET_MAPPED)) |
|
| 1873 readjust_columns(tree); |
|
| 1874 } |
|
| 1875 |
|
| 1876 void gnt_tree_set_column_resizable(GntTree *tree, int col, gboolean res) |
|
| 1877 { |
|
| 1878 g_return_if_fail(col < tree->ncol); |
|
| 1879 set_column_flag(tree, col, GNT_TREE_COLUMN_FIXED_SIZE, !res); |
|
| 1880 } |
|
| 1881 |
|
| 1882 void gnt_tree_set_column_is_binary(GntTree *tree, int col, gboolean bin) |
|
| 1883 { |
|
| 1884 g_return_if_fail(col < tree->ncol); |
|
| 1885 set_column_flag(tree, col, GNT_TREE_COLUMN_BINARY_DATA, bin); |
|
| 1886 } |
|
| 1887 |
|
| 1888 void gnt_tree_set_column_is_right_aligned(GntTree *tree, int col, gboolean right) |
|
| 1889 { |
|
| 1890 g_return_if_fail(col < tree->ncol); |
|
| 1891 set_column_flag(tree, col, GNT_TREE_COLUMN_RIGHT_ALIGNED, right); |
|
| 1892 } |
|
| 1893 |
|
| 1894 void gnt_tree_set_column_width_ratio(GntTree *tree, int cols[]) |
|
| 1895 { |
|
| 1896 int i; |
|
| 1897 for (i = 0; i < tree->ncol && cols[i]; i++) { |
|
| 1898 tree->columns[i].width_ratio = cols[i]; |
|
| 1899 } |
|
| 1900 } |
|
| 1901 |
|
| 1902 void gnt_tree_set_search_column(GntTree *tree, int col) |
|
| 1903 { |
|
| 1904 g_return_if_fail(col < tree->ncol); |
|
| 1905 g_return_if_fail(!BINARY_DATA(tree, col)); |
|
| 1906 tree->priv->search_column = col; |
|
| 1907 } |
|
| 1908 |
|
| 1909 gboolean gnt_tree_is_searching(GntTree *tree) |
|
| 1910 { |
|
| 1911 return (tree->priv->search != NULL); |
|
| 1912 } |
|
| 1913 |
|
| 1914 void gnt_tree_set_search_function(GntTree *tree, |
|
| 1915 gboolean (*func)(GntTree *tree, gpointer key, const char *search, const char *current)) |
|
| 1916 { |
|
| 1917 tree->priv->search_func = func; |
|
| 1918 } |
|
| 1919 |
|
| 1920 gpointer gnt_tree_get_parent_key(GntTree *tree, gpointer key) |
|
| 1921 { |
|
| 1922 GntTreeRow *row = g_hash_table_lookup(tree->hash, key); |
|
| 1923 return (row && row->parent) ? row->parent->key : NULL; |
|
| 1924 } |
|
| 1925 |
|
| 1926 gpointer gnt_tree_row_get_key(GntTree *tree, GntTreeRow *row) |
|
| 1927 { |
|
| 1928 g_return_val_if_fail(row && row->tree == tree, NULL); |
|
| 1929 return row->key; |
|
| 1930 } |
|
| 1931 |
|
| 1932 GntTreeRow * gnt_tree_row_get_next(GntTree *tree, GntTreeRow *row) |
|
| 1933 { |
|
| 1934 g_return_val_if_fail(row && row->tree == tree, NULL); |
|
| 1935 return row->next; |
|
| 1936 } |
|
| 1937 |
|
| 1938 GntTreeRow * gnt_tree_row_get_prev(GntTree *tree, GntTreeRow *row) |
|
| 1939 { |
|
| 1940 g_return_val_if_fail(row && row->tree == tree, NULL); |
|
| 1941 return row->prev; |
|
| 1942 } |
|
| 1943 |
|
| 1944 GntTreeRow * gnt_tree_row_get_child(GntTree *tree, GntTreeRow *row) |
|
| 1945 { |
|
| 1946 g_return_val_if_fail(row && row->tree == tree, NULL); |
|
| 1947 return row->child; |
|
| 1948 } |
|
| 1949 |
|
| 1950 GntTreeRow * gnt_tree_row_get_parent(GntTree *tree, GntTreeRow *row) |
|
| 1951 { |
|
| 1952 g_return_val_if_fail(row && row->tree == tree, NULL); |
|
| 1953 return row->parent; |
|
| 1954 } |
|
| 1955 |
|
| 1956 /************************************************************************** |
|
| 1957 * GntTreeRow GBoxed API |
|
| 1958 **************************************************************************/ |
|
| 1959 static GntTreeRow * |
|
| 1960 gnt_tree_row_ref(GntTreeRow *row) |
|
| 1961 { |
|
| 1962 g_return_val_if_fail(row != NULL, NULL); |
|
| 1963 |
|
| 1964 row->box_count++; |
|
| 1965 |
|
| 1966 return row; |
|
| 1967 } |
|
| 1968 |
|
| 1969 static void |
|
| 1970 gnt_tree_row_unref(GntTreeRow *row) |
|
| 1971 { |
|
| 1972 g_return_if_fail(row != NULL); |
|
| 1973 g_return_if_fail(row->box_count >= 0); |
|
| 1974 |
|
| 1975 if (!row->box_count--) |
|
| 1976 free_tree_row(row); |
|
| 1977 } |
|
| 1978 |
|
| 1979 GType |
|
| 1980 gnt_tree_row_get_type(void) |
|
| 1981 { |
|
| 1982 static GType type = 0; |
|
| 1983 |
|
| 1984 if (type == 0) { |
|
| 1985 type = g_boxed_type_register_static("GntTreeRow", |
|
| 1986 (GBoxedCopyFunc)gnt_tree_row_ref, |
|
| 1987 (GBoxedFreeFunc)gnt_tree_row_unref); |
|
| 1988 } |
|
| 1989 |
|
| 1990 return type; |
|
| 1991 } |
|