linux/tools/perf/ui/browser.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2#include "../util/string2.h"
   3#include "../util/config.h"
   4#include "libslang.h"
   5#include "ui.h"
   6#include "util.h"
   7#include <linux/compiler.h>
   8#include <linux/list.h>
   9#include <linux/rbtree.h>
  10#include <linux/string.h>
  11#include <stdlib.h>
  12#include <sys/ttydefaults.h>
  13#include "browser.h"
  14#include "helpline.h"
  15#include "keysyms.h"
  16#include "../util/color.h"
  17#include <linux/ctype.h>
  18#include <linux/zalloc.h>
  19
  20static int ui_browser__percent_color(struct ui_browser *browser,
  21                                     double percent, bool current)
  22{
  23        if (current && (!browser->use_navkeypressed || browser->navkeypressed))
  24                return HE_COLORSET_SELECTED;
  25        if (percent >= MIN_RED)
  26                return HE_COLORSET_TOP;
  27        if (percent >= MIN_GREEN)
  28                return HE_COLORSET_MEDIUM;
  29        return HE_COLORSET_NORMAL;
  30}
  31
  32int ui_browser__set_color(struct ui_browser *browser, int color)
  33{
  34        int ret = browser->current_color;
  35        browser->current_color = color;
  36        SLsmg_set_color(color);
  37        return ret;
  38}
  39
  40void ui_browser__set_percent_color(struct ui_browser *browser,
  41                                   double percent, bool current)
  42{
  43         int color = ui_browser__percent_color(browser, percent, current);
  44         ui_browser__set_color(browser, color);
  45}
  46
  47void ui_browser__gotorc_title(struct ui_browser *browser, int y, int x)
  48{
  49        SLsmg_gotorc(browser->y + y, browser->x + x);
  50}
  51
  52void ui_browser__gotorc(struct ui_browser *browser, int y, int x)
  53{
  54        SLsmg_gotorc(browser->y + y + browser->extra_title_lines, browser->x + x);
  55}
  56
  57void ui_browser__write_nstring(struct ui_browser *browser __maybe_unused, const char *msg,
  58                               unsigned int width)
  59{
  60        slsmg_write_nstring(msg, width);
  61}
  62
  63void ui_browser__vprintf(struct ui_browser *browser __maybe_unused, const char *fmt, va_list args)
  64{
  65        slsmg_vprintf(fmt, args);
  66}
  67
  68void ui_browser__printf(struct ui_browser *browser __maybe_unused, const char *fmt, ...)
  69{
  70        va_list args;
  71
  72        va_start(args, fmt);
  73        ui_browser__vprintf(browser, fmt, args);
  74        va_end(args);
  75}
  76
  77static struct list_head *
  78ui_browser__list_head_filter_entries(struct ui_browser *browser,
  79                                     struct list_head *pos)
  80{
  81        do {
  82                if (!browser->filter || !browser->filter(browser, pos))
  83                        return pos;
  84                pos = pos->next;
  85        } while (pos != browser->entries);
  86
  87        return NULL;
  88}
  89
  90static struct list_head *
  91ui_browser__list_head_filter_prev_entries(struct ui_browser *browser,
  92                                          struct list_head *pos)
  93{
  94        do {
  95                if (!browser->filter || !browser->filter(browser, pos))
  96                        return pos;
  97                pos = pos->prev;
  98        } while (pos != browser->entries);
  99
 100        return NULL;
 101}
 102
 103void ui_browser__list_head_seek(struct ui_browser *browser, off_t offset, int whence)
 104{
 105        struct list_head *head = browser->entries;
 106        struct list_head *pos;
 107
 108        if (browser->nr_entries == 0)
 109                return;
 110
 111        switch (whence) {
 112        case SEEK_SET:
 113                pos = ui_browser__list_head_filter_entries(browser, head->next);
 114                break;
 115        case SEEK_CUR:
 116                pos = browser->top;
 117                break;
 118        case SEEK_END:
 119                pos = ui_browser__list_head_filter_prev_entries(browser, head->prev);
 120                break;
 121        default:
 122                return;
 123        }
 124
 125        assert(pos != NULL);
 126
 127        if (offset > 0) {
 128                while (offset-- != 0)
 129                        pos = ui_browser__list_head_filter_entries(browser, pos->next);
 130        } else {
 131                while (offset++ != 0)
 132                        pos = ui_browser__list_head_filter_prev_entries(browser, pos->prev);
 133        }
 134
 135        browser->top = pos;
 136}
 137
 138void ui_browser__rb_tree_seek(struct ui_browser *browser, off_t offset, int whence)
 139{
 140        struct rb_root *root = browser->entries;
 141        struct rb_node *nd;
 142
 143        switch (whence) {
 144        case SEEK_SET:
 145                nd = rb_first(root);
 146                break;
 147        case SEEK_CUR:
 148                nd = browser->top;
 149                break;
 150        case SEEK_END:
 151                nd = rb_last(root);
 152                break;
 153        default:
 154                return;
 155        }
 156
 157        if (offset > 0) {
 158                while (offset-- != 0)
 159                        nd = rb_next(nd);
 160        } else {
 161                while (offset++ != 0)
 162                        nd = rb_prev(nd);
 163        }
 164
 165        browser->top = nd;
 166}
 167
 168unsigned int ui_browser__rb_tree_refresh(struct ui_browser *browser)
 169{
 170        struct rb_node *nd;
 171        int row = 0;
 172
 173        if (browser->top == NULL)
 174                browser->top = rb_first(browser->entries);
 175
 176        nd = browser->top;
 177
 178        while (nd != NULL) {
 179                ui_browser__gotorc(browser, row, 0);
 180                browser->write(browser, nd, row);
 181                if (++row == browser->rows)
 182                        break;
 183                nd = rb_next(nd);
 184        }
 185
 186        return row;
 187}
 188
 189bool ui_browser__is_current_entry(struct ui_browser *browser, unsigned row)
 190{
 191        return browser->top_idx + row == browser->index;
 192}
 193
 194void ui_browser__refresh_dimensions(struct ui_browser *browser)
 195{
 196        browser->width = SLtt_Screen_Cols - 1;
 197        browser->height = browser->rows = SLtt_Screen_Rows - 2;
 198        browser->rows -= browser->extra_title_lines;
 199        browser->y = 1;
 200        browser->x = 0;
 201}
 202
 203void ui_browser__handle_resize(struct ui_browser *browser)
 204{
 205        ui__refresh_dimensions(false);
 206        ui_browser__show(browser, browser->title, ui_helpline__current);
 207        ui_browser__refresh(browser);
 208}
 209
 210int ui_browser__warning(struct ui_browser *browser, int timeout,
 211                        const char *format, ...)
 212{
 213        va_list args;
 214        char *text;
 215        int key = 0, err;
 216
 217        va_start(args, format);
 218        err = vasprintf(&text, format, args);
 219        va_end(args);
 220
 221        if (err < 0) {
 222                va_start(args, format);
 223                ui_helpline__vpush(format, args);
 224                va_end(args);
 225        } else {
 226                while ((key = ui__question_window("Warning!", text,
 227                                                   "Press any key...",
 228                                                   timeout)) == K_RESIZE)
 229                        ui_browser__handle_resize(browser);
 230                free(text);
 231        }
 232
 233        return key;
 234}
 235
 236int ui_browser__help_window(struct ui_browser *browser, const char *text)
 237{
 238        int key;
 239
 240        while ((key = ui__help_window(text)) == K_RESIZE)
 241                ui_browser__handle_resize(browser);
 242
 243        return key;
 244}
 245
 246bool ui_browser__dialog_yesno(struct ui_browser *browser, const char *text)
 247{
 248        int key;
 249
 250        while ((key = ui__dialog_yesno(text)) == K_RESIZE)
 251                ui_browser__handle_resize(browser);
 252
 253        return key == K_ENTER || toupper(key) == 'Y';
 254}
 255
 256void ui_browser__reset_index(struct ui_browser *browser)
 257{
 258        browser->index = browser->top_idx = 0;
 259        browser->seek(browser, 0, SEEK_SET);
 260}
 261
 262void __ui_browser__show_title(struct ui_browser *browser, const char *title)
 263{
 264        SLsmg_gotorc(0, 0);
 265        ui_browser__set_color(browser, HE_COLORSET_ROOT);
 266        ui_browser__write_nstring(browser, title, browser->width + 1);
 267}
 268
 269void ui_browser__show_title(struct ui_browser *browser, const char *title)
 270{
 271        pthread_mutex_lock(&ui__lock);
 272        __ui_browser__show_title(browser, title);
 273        pthread_mutex_unlock(&ui__lock);
 274}
 275
 276int ui_browser__show(struct ui_browser *browser, const char *title,
 277                     const char *helpline, ...)
 278{
 279        int err;
 280        va_list ap;
 281
 282        if (browser->refresh_dimensions == NULL)
 283                browser->refresh_dimensions = ui_browser__refresh_dimensions;
 284
 285        browser->refresh_dimensions(browser);
 286
 287        pthread_mutex_lock(&ui__lock);
 288        __ui_browser__show_title(browser, title);
 289
 290        browser->title = title;
 291        zfree(&browser->helpline);
 292
 293        va_start(ap, helpline);
 294        err = vasprintf(&browser->helpline, helpline, ap);
 295        va_end(ap);
 296        if (err > 0)
 297                ui_helpline__push(browser->helpline);
 298        pthread_mutex_unlock(&ui__lock);
 299        return err ? 0 : -1;
 300}
 301
 302void ui_browser__hide(struct ui_browser *browser)
 303{
 304        pthread_mutex_lock(&ui__lock);
 305        ui_helpline__pop();
 306        zfree(&browser->helpline);
 307        pthread_mutex_unlock(&ui__lock);
 308}
 309
 310static void ui_browser__scrollbar_set(struct ui_browser *browser)
 311{
 312        int height = browser->height, h = 0, pct = 0,
 313            col = browser->width,
 314            row = 0;
 315
 316        if (browser->nr_entries > 1) {
 317                pct = ((browser->index * (browser->height - 1)) /
 318                       (browser->nr_entries - 1));
 319        }
 320
 321        SLsmg_set_char_set(1);
 322
 323        while (h < height) {
 324                ui_browser__gotorc(browser, row++, col);
 325                SLsmg_write_char(h == pct ? SLSMG_DIAMOND_CHAR : SLSMG_CKBRD_CHAR);
 326                ++h;
 327        }
 328
 329        SLsmg_set_char_set(0);
 330}
 331
 332static int __ui_browser__refresh(struct ui_browser *browser)
 333{
 334        int row;
 335        int width = browser->width;
 336
 337        row = browser->refresh(browser);
 338        ui_browser__set_color(browser, HE_COLORSET_NORMAL);
 339
 340        if (!browser->use_navkeypressed || browser->navkeypressed)
 341                ui_browser__scrollbar_set(browser);
 342        else
 343                width += 1;
 344
 345        SLsmg_fill_region(browser->y + row + browser->extra_title_lines, browser->x,
 346                          browser->rows - row, width, ' ');
 347
 348        if (browser->nr_entries == 0 && browser->no_samples_msg)
 349                __ui__info_window(NULL, browser->no_samples_msg, NULL);
 350        return 0;
 351}
 352
 353int ui_browser__refresh(struct ui_browser *browser)
 354{
 355        pthread_mutex_lock(&ui__lock);
 356        __ui_browser__refresh(browser);
 357        pthread_mutex_unlock(&ui__lock);
 358
 359        return 0;
 360}
 361
 362/*
 363 * Here we're updating nr_entries _after_ we started browsing, i.e.  we have to
 364 * forget about any reference to any entry in the underlying data structure,
 365 * that is why we do a SEEK_SET. Think about 'perf top' in the hists browser
 366 * after an output_resort and hist decay.
 367 */
 368void ui_browser__update_nr_entries(struct ui_browser *browser, u32 nr_entries)
 369{
 370        off_t offset = nr_entries - browser->nr_entries;
 371
 372        browser->nr_entries = nr_entries;
 373
 374        if (offset < 0) {
 375                if (browser->top_idx < (u64)-offset)
 376                        offset = -browser->top_idx;
 377
 378                browser->index += offset;
 379                browser->top_idx += offset;
 380        }
 381
 382        browser->top = NULL;
 383        browser->seek(browser, browser->top_idx, SEEK_SET);
 384}
 385
 386int ui_browser__run(struct ui_browser *browser, int delay_secs)
 387{
 388        int err, key;
 389
 390        while (1) {
 391                off_t offset;
 392
 393                pthread_mutex_lock(&ui__lock);
 394                err = __ui_browser__refresh(browser);
 395                SLsmg_refresh();
 396                pthread_mutex_unlock(&ui__lock);
 397                if (err < 0)
 398                        break;
 399
 400                key = ui__getch(delay_secs);
 401
 402                if (key == K_RESIZE) {
 403                        ui__refresh_dimensions(false);
 404                        browser->refresh_dimensions(browser);
 405                        __ui_browser__show_title(browser, browser->title);
 406                        ui_helpline__puts(browser->helpline);
 407                        continue;
 408                }
 409
 410                if (browser->use_navkeypressed && !browser->navkeypressed) {
 411                        if (key == K_DOWN || key == K_UP ||
 412                            (browser->columns && (key == K_LEFT || key == K_RIGHT)) ||
 413                            key == K_PGDN || key == K_PGUP ||
 414                            key == K_HOME || key == K_END ||
 415                            key == ' ') {
 416                                browser->navkeypressed = true;
 417                                continue;
 418                        } else
 419                                return key;
 420                }
 421
 422                switch (key) {
 423                case K_DOWN:
 424                        if (browser->index == browser->nr_entries - 1)
 425                                break;
 426                        ++browser->index;
 427                        if (browser->index == browser->top_idx + browser->rows) {
 428                                ++browser->top_idx;
 429                                browser->seek(browser, +1, SEEK_CUR);
 430                        }
 431                        break;
 432                case K_UP:
 433                        if (browser->index == 0)
 434                                break;
 435                        --browser->index;
 436                        if (browser->index < browser->top_idx) {
 437                                --browser->top_idx;
 438                                browser->seek(browser, -1, SEEK_CUR);
 439                        }
 440                        break;
 441                case K_RIGHT:
 442                        if (!browser->columns)
 443                                goto out;
 444                        if (browser->horiz_scroll < browser->columns - 1)
 445                                ++browser->horiz_scroll;
 446                        break;
 447                case K_LEFT:
 448                        if (!browser->columns)
 449                                goto out;
 450                        if (browser->horiz_scroll != 0)
 451                                --browser->horiz_scroll;
 452                        break;
 453                case K_PGDN:
 454                case ' ':
 455                        if (browser->top_idx + browser->rows > browser->nr_entries - 1)
 456                                break;
 457
 458                        offset = browser->rows;
 459                        if (browser->index + offset > browser->nr_entries - 1)
 460                                offset = browser->nr_entries - 1 - browser->index;
 461                        browser->index += offset;
 462                        browser->top_idx += offset;
 463                        browser->seek(browser, +offset, SEEK_CUR);
 464                        break;
 465                case K_PGUP:
 466                        if (browser->top_idx == 0)
 467                                break;
 468
 469                        if (browser->top_idx < browser->rows)
 470                                offset = browser->top_idx;
 471                        else
 472                                offset = browser->rows;
 473
 474                        browser->index -= offset;
 475                        browser->top_idx -= offset;
 476                        browser->seek(browser, -offset, SEEK_CUR);
 477                        break;
 478                case K_HOME:
 479                        ui_browser__reset_index(browser);
 480                        break;
 481                case K_END:
 482                        offset = browser->rows - 1;
 483                        if (offset >= browser->nr_entries)
 484                                offset = browser->nr_entries - 1;
 485
 486                        browser->index = browser->nr_entries - 1;
 487                        browser->top_idx = browser->index - offset;
 488                        browser->seek(browser, -offset, SEEK_END);
 489                        break;
 490                default:
 491                out:
 492                        return key;
 493                }
 494        }
 495        return -1;
 496}
 497
 498unsigned int ui_browser__list_head_refresh(struct ui_browser *browser)
 499{
 500        struct list_head *pos;
 501        struct list_head *head = browser->entries;
 502        int row = 0;
 503
 504        if (browser->top == NULL || browser->top == browser->entries)
 505                browser->top = ui_browser__list_head_filter_entries(browser, head->next);
 506
 507        pos = browser->top;
 508
 509        list_for_each_from(pos, head) {
 510                if (!browser->filter || !browser->filter(browser, pos)) {
 511                        ui_browser__gotorc(browser, row, 0);
 512                        browser->write(browser, pos, row);
 513                        if (++row == browser->rows)
 514                                break;
 515                }
 516        }
 517
 518        return row;
 519}
 520
 521static struct ui_browser_colorset {
 522        const char *name, *fg, *bg;
 523        int colorset;
 524} ui_browser__colorsets[] = {
 525        {
 526                .colorset = HE_COLORSET_TOP,
 527                .name     = "top",
 528                .fg       = "red",
 529                .bg       = "default",
 530        },
 531        {
 532                .colorset = HE_COLORSET_MEDIUM,
 533                .name     = "medium",
 534                .fg       = "green",
 535                .bg       = "default",
 536        },
 537        {
 538                .colorset = HE_COLORSET_NORMAL,
 539                .name     = "normal",
 540                .fg       = "default",
 541                .bg       = "default",
 542        },
 543        {
 544                .colorset = HE_COLORSET_SELECTED,
 545                .name     = "selected",
 546                .fg       = "black",
 547                .bg       = "yellow",
 548        },
 549        {
 550                .colorset = HE_COLORSET_JUMP_ARROWS,
 551                .name     = "jump_arrows",
 552                .fg       = "blue",
 553                .bg       = "default",
 554        },
 555        {
 556                .colorset = HE_COLORSET_ADDR,
 557                .name     = "addr",
 558                .fg       = "magenta",
 559                .bg       = "default",
 560        },
 561        {
 562                .colorset = HE_COLORSET_ROOT,
 563                .name     = "root",
 564                .fg       = "white",
 565                .bg       = "blue",
 566        },
 567        {
 568                .name = NULL,
 569        }
 570};
 571
 572
 573static int ui_browser__color_config(const char *var, const char *value,
 574                                    void *data __maybe_unused)
 575{
 576        char *fg = NULL, *bg;
 577        int i;
 578
 579        /* same dir for all commands */
 580        if (!strstarts(var, "colors.") != 0)
 581                return 0;
 582
 583        for (i = 0; ui_browser__colorsets[i].name != NULL; ++i) {
 584                const char *name = var + 7;
 585
 586                if (strcmp(ui_browser__colorsets[i].name, name) != 0)
 587                        continue;
 588
 589                fg = strdup(value);
 590                if (fg == NULL)
 591                        break;
 592
 593                bg = strchr(fg, ',');
 594                if (bg == NULL)
 595                        break;
 596
 597                *bg = '\0';
 598                bg = skip_spaces(bg + 1);
 599                ui_browser__colorsets[i].bg = bg;
 600                ui_browser__colorsets[i].fg = fg;
 601                return 0;
 602        }
 603
 604        free(fg);
 605        return -1;
 606}
 607
 608void ui_browser__argv_seek(struct ui_browser *browser, off_t offset, int whence)
 609{
 610        switch (whence) {
 611        case SEEK_SET:
 612                browser->top = browser->entries;
 613                break;
 614        case SEEK_CUR:
 615                browser->top = (char **)browser->top + offset;
 616                break;
 617        case SEEK_END:
 618                browser->top = (char **)browser->entries + browser->nr_entries - 1 + offset;
 619                break;
 620        default:
 621                return;
 622        }
 623        assert((char **)browser->top < (char **)browser->entries + browser->nr_entries);
 624        assert((char **)browser->top >= (char **)browser->entries);
 625}
 626
 627unsigned int ui_browser__argv_refresh(struct ui_browser *browser)
 628{
 629        unsigned int row = 0, idx = browser->top_idx;
 630        char **pos;
 631
 632        if (browser->top == NULL)
 633                browser->top = browser->entries;
 634
 635        pos = (char **)browser->top;
 636        while (idx < browser->nr_entries &&
 637               row < (unsigned)SLtt_Screen_Rows - 1) {
 638                assert(pos < (char **)browser->entries + browser->nr_entries);
 639                if (!browser->filter || !browser->filter(browser, *pos)) {
 640                        ui_browser__gotorc(browser, row, 0);
 641                        browser->write(browser, pos, row);
 642                        if (++row == browser->rows)
 643                                break;
 644                }
 645
 646                ++idx;
 647                ++pos;
 648        }
 649
 650        return row;
 651}
 652
 653void __ui_browser__vline(struct ui_browser *browser, unsigned int column,
 654                         u16 start, u16 end)
 655{
 656        SLsmg_set_char_set(1);
 657        ui_browser__gotorc(browser, start, column);
 658        SLsmg_draw_vline(end - start + 1);
 659        SLsmg_set_char_set(0);
 660}
 661
 662void ui_browser__write_graph(struct ui_browser *browser __maybe_unused,
 663                             int graph)
 664{
 665        SLsmg_set_char_set(1);
 666        SLsmg_write_char(graph);
 667        SLsmg_set_char_set(0);
 668}
 669
 670static void __ui_browser__line_arrow_up(struct ui_browser *browser,
 671                                        unsigned int column,
 672                                        u64 start, u64 end)
 673{
 674        unsigned int row, end_row;
 675
 676        SLsmg_set_char_set(1);
 677
 678        if (start < browser->top_idx + browser->rows) {
 679                row = start - browser->top_idx;
 680                ui_browser__gotorc(browser, row, column);
 681                SLsmg_write_char(SLSMG_LLCORN_CHAR);
 682                ui_browser__gotorc(browser, row, column + 1);
 683                SLsmg_draw_hline(2);
 684
 685                if (row-- == 0)
 686                        goto out;
 687        } else
 688                row = browser->rows - 1;
 689
 690        if (end > browser->top_idx)
 691                end_row = end - browser->top_idx;
 692        else
 693                end_row = 0;
 694
 695        ui_browser__gotorc(browser, end_row, column);
 696        SLsmg_draw_vline(row - end_row + 1);
 697
 698        ui_browser__gotorc(browser, end_row, column);
 699        if (end >= browser->top_idx) {
 700                SLsmg_write_char(SLSMG_ULCORN_CHAR);
 701                ui_browser__gotorc(browser, end_row, column + 1);
 702                SLsmg_write_char(SLSMG_HLINE_CHAR);
 703                ui_browser__gotorc(browser, end_row, column + 2);
 704                SLsmg_write_char(SLSMG_RARROW_CHAR);
 705        }
 706out:
 707        SLsmg_set_char_set(0);
 708}
 709
 710static void __ui_browser__line_arrow_down(struct ui_browser *browser,
 711                                          unsigned int column,
 712                                          u64 start, u64 end)
 713{
 714        unsigned int row, end_row;
 715
 716        SLsmg_set_char_set(1);
 717
 718        if (start >= browser->top_idx) {
 719                row = start - browser->top_idx;
 720                ui_browser__gotorc(browser, row, column);
 721                SLsmg_write_char(SLSMG_ULCORN_CHAR);
 722                ui_browser__gotorc(browser, row, column + 1);
 723                SLsmg_draw_hline(2);
 724
 725                if (++row == 0)
 726                        goto out;
 727        } else
 728                row = 0;
 729
 730        if (end >= browser->top_idx + browser->rows)
 731                end_row = browser->rows - 1;
 732        else
 733                end_row = end - browser->top_idx;
 734
 735        ui_browser__gotorc(browser, row, column);
 736        SLsmg_draw_vline(end_row - row + 1);
 737
 738        ui_browser__gotorc(browser, end_row, column);
 739        if (end < browser->top_idx + browser->rows) {
 740                SLsmg_write_char(SLSMG_LLCORN_CHAR);
 741                ui_browser__gotorc(browser, end_row, column + 1);
 742                SLsmg_write_char(SLSMG_HLINE_CHAR);
 743                ui_browser__gotorc(browser, end_row, column + 2);
 744                SLsmg_write_char(SLSMG_RARROW_CHAR);
 745        }
 746out:
 747        SLsmg_set_char_set(0);
 748}
 749
 750void __ui_browser__line_arrow(struct ui_browser *browser, unsigned int column,
 751                              u64 start, u64 end)
 752{
 753        if (start > end)
 754                __ui_browser__line_arrow_up(browser, column, start, end);
 755        else
 756                __ui_browser__line_arrow_down(browser, column, start, end);
 757}
 758
 759void ui_browser__mark_fused(struct ui_browser *browser, unsigned int column,
 760                            unsigned int row, bool arrow_down)
 761{
 762        unsigned int end_row;
 763
 764        if (row >= browser->top_idx)
 765                end_row = row - browser->top_idx;
 766        else
 767                return;
 768
 769        SLsmg_set_char_set(1);
 770
 771        if (arrow_down) {
 772                ui_browser__gotorc(browser, end_row, column - 1);
 773                SLsmg_write_char(SLSMG_ULCORN_CHAR);
 774                ui_browser__gotorc(browser, end_row, column);
 775                SLsmg_draw_hline(2);
 776                ui_browser__gotorc(browser, end_row + 1, column - 1);
 777                SLsmg_write_char(SLSMG_LTEE_CHAR);
 778        } else {
 779                ui_browser__gotorc(browser, end_row, column - 1);
 780                SLsmg_write_char(SLSMG_LTEE_CHAR);
 781                ui_browser__gotorc(browser, end_row, column);
 782                SLsmg_draw_hline(2);
 783        }
 784
 785        SLsmg_set_char_set(0);
 786}
 787
 788void ui_browser__init(void)
 789{
 790        int i = 0;
 791
 792        perf_config(ui_browser__color_config, NULL);
 793
 794        while (ui_browser__colorsets[i].name) {
 795                struct ui_browser_colorset *c = &ui_browser__colorsets[i++];
 796                sltt_set_color(c->colorset, c->name, c->fg, c->bg);
 797        }
 798}
 799