diff options
author | dshaligram <dshaligram@c06c8d41-db1a-0410-9941-cceddc491573> | 2006-11-22 08:41:20 +0000 |
---|---|---|
committer | dshaligram <dshaligram@c06c8d41-db1a-0410-9941-cceddc491573> | 2006-11-22 08:41:20 +0000 |
commit | 1d0f57cbceb778139ca215cc4fcfd1584951f6dd (patch) | |
tree | cafd60c944c51fcce778aa5d6912bc548c518339 /crawl-ref/source/menu.cc | |
parent | 6f5e187a9e5cd348296dba2fd89d2e206e775a01 (diff) | |
download | crawl-ref-1d0f57cbceb778139ca215cc4fcfd1584951f6dd.tar.gz crawl-ref-1d0f57cbceb778139ca215cc4fcfd1584951f6dd.zip |
Merged stone_soup r15:451 into trunk.
git-svn-id: https://crawl-ref.svn.sourceforge.net/svnroot/crawl-ref/trunk@452 c06c8d41-db1a-0410-9941-cceddc491573
Diffstat (limited to 'crawl-ref/source/menu.cc')
-rw-r--r-- | crawl-ref/source/menu.cc | 880 |
1 files changed, 793 insertions, 87 deletions
diff --git a/crawl-ref/source/menu.cc b/crawl-ref/source/menu.cc index 0728980058..c804c5343b 100644 --- a/crawl-ref/source/menu.cc +++ b/crawl-ref/source/menu.cc @@ -1,23 +1,37 @@ +/* + * File: menu.cc + * Summary: Menus and associated malarkey. + * Written by: Darshan Shaligram + * + * Modified for Crawl Reference by $Author$ on $Date$ + */ #include <cctype> +#include "AppHdr.h" #include "menu.h" #include "macro.h" #include "view.h" +#include "initfile.h" Menu::Menu( int _flags ) - : selitem_text(NULL), + : f_selitem(NULL), + f_drawitem(NULL), + f_keyfilter(NULL), title(NULL), flags(_flags), first_entry(0), y_offset(0), pagesize(0), + max_pagesize(0), + more("-more-", true), items(), - sel(NULL), + sel(), select_filter(), highlighter(new MenuHighlighter), num(-1), lastch(0), alive(false) { + set_flags(flags); } Menu::~Menu() @@ -28,6 +42,33 @@ Menu::~Menu() delete highlighter; } +void Menu::clear() +{ + for (int i = 0, count = items.size(); i < count; ++i) + delete items[i]; + items.clear(); +} + +void Menu::set_maxpagesize(int max) +{ + max_pagesize = max; +} + +void Menu::set_flags(int new_flags, bool use_options) +{ + flags = new_flags; + if (use_options) + { + if (Options.easy_exit_menu) + flags |= MF_EASY_EXIT; + } +} + +void Menu::set_more(const formatted_string &fs) +{ + more = fs; +} + void Menu::set_highlighter( MenuHighlighter *mh ) { if (highlighter != mh) @@ -54,14 +95,17 @@ void Menu::reset() first_entry = 0; } -std::vector<MenuEntry *> Menu::show() +std::vector<MenuEntry *> Menu::show(bool reuse_selections) { - std::vector< MenuEntry * > selected; - - deselect_all(false); + if (reuse_selections) + get_selected(&sel); + else + deselect_all(false); - // Lose lines for the title + room for more. - pagesize = get_number_of_lines() - 2; + // Lose lines for the title + room for -more- line. + pagesize = get_number_of_lines() - !!title - 1; + if (max_pagesize > 0 && pagesize > max_pagesize) + pagesize = max_pagesize; #ifdef DOS_TERM char buffer[4600]; @@ -70,19 +114,18 @@ std::vector<MenuEntry *> Menu::show() window(1, 1, 80, 25); #endif - do_menu( &selected ); + do_menu(); #ifdef DOS_TERM puttext(1, 1, 80, 25, buffer); #endif - return selected; + return (sel); } -void Menu::do_menu( std::vector<MenuEntry*> *selected ) +void Menu::do_menu() { - sel = selected; - draw_menu( selected ); + draw_menu(); alive = true; while (alive) @@ -96,71 +139,88 @@ void Menu::do_menu( std::vector<MenuEntry*> *selected ) bool Menu::is_set(int flag) const { - if (flag == MF_EASY_EXIT && Options.easy_exit_menu) - return true; return (flags & flag) == flag; } +int Menu::pre_process(int k) +{ + return (k); +} + +int Menu::post_process(int k) +{ + return (k); +} + bool Menu::process_key( int keyin ) { if (items.size() == 0) return false; bool nav = false, repaint = false; + + if (f_keyfilter) + keyin = (*f_keyfilter)(keyin); + + keyin = pre_process(keyin); + switch (keyin) { - case CK_ENTER: - return false; - case CK_ESCAPE: - sel->clear(); - lastch = keyin; - return false; - case ' ': case CK_PGDN: case '>': case '\'': - nav = true; - repaint = page_down(); - if (!repaint && flags && !is_set(MF_EASY_EXIT)) - { - repaint = first_entry != 0; - first_entry = 0; - } - break; - case CK_PGUP: case '<': case ';': - nav = true; - repaint = page_up(); - break; - case CK_UP: - nav = true; - repaint = line_up(); - break; - case CK_DOWN: - nav = true; - repaint = line_down(); - break; - default: - lastch = keyin; + case 0: + return true; + case CK_ENTER: + return false; + case CK_ESCAPE: + sel.clear(); + lastch = keyin; + return false; + case ' ': case CK_PGDN: case '>': case '\'': + nav = true; + repaint = page_down(); + if (!repaint && !is_set(MF_EASY_EXIT) && !is_set(MF_NOWRAP)) + { + repaint = first_entry != 0; + first_entry = 0; + } + break; + case CK_PGUP: case '<': case ';': + nav = true; + repaint = page_up(); + break; + case CK_UP: + nav = true; + repaint = line_up(); + break; + case CK_DOWN: + nav = true; + repaint = line_down(); + break; + default: + keyin = post_process(keyin); + lastch = keyin; - // If no selection at all is allowed, exit now. - if (!(flags & (MF_SINGLESELECT | MF_MULTISELECT))) - return false; + // If no selection at all is allowed, exit now. + if (!(flags & (MF_SINGLESELECT | MF_MULTISELECT))) + return false; - if (!is_set(MF_NO_SELECT_QTY) && isdigit( keyin )) - { - if (num > 999) - num = -1; - num = (num == -1)? keyin - '0' : - num * 10 + keyin - '0'; - } - - select_items( keyin, num ); - get_selected( sel ); - if (sel->size() == 1 && (flags & MF_SINGLESELECT)) - return false; - draw_select_count( sel->size() ); + if (!is_set(MF_NO_SELECT_QTY) && isdigit( keyin )) + { + if (num > 999) + num = -1; + num = (num == -1)? keyin - '0' : + num * 10 + keyin - '0'; + } + + select_items( keyin, num ); + get_selected( &sel ); + if (sel.size() == 1 && (flags & MF_SINGLESELECT)) + return false; + draw_select_count( sel.size() ); - if (flags & MF_ANYPRINTABLE && !isdigit( keyin )) - return false; + if (flags & MF_ANYPRINTABLE && !isdigit( keyin )) + return false; - break; + break; } if (!isdigit( keyin )) @@ -169,11 +229,11 @@ bool Menu::process_key( int keyin ) if (nav) { if (repaint) - draw_menu( sel ); + draw_menu(); // Easy exit should not kill the menu if there are selected items. - else if (sel->empty() && (!flags || is_set(MF_EASY_EXIT))) + else if (sel.empty() && is_set(MF_EASY_EXIT)) { - sel->clear(); + sel.clear(); return false; } } @@ -188,32 +248,32 @@ bool Menu::draw_title_suffix( const std::string &s, bool titlefirst ) draw_title(); int x = wherex(); - if (x > GXM || x < 1) + if (x > get_number_of_cols() || x < 1) { gotoxy(oldx, oldy); return false; } - // Note: 1 <= x <= GXM; we have no fear of overflow. - unsigned avail_width = GXM - x + 1; + // Note: 1 <= x <= get_number_of_cols(); we have no fear of overflow. + unsigned avail_width = get_number_of_cols() - x + 1; std::string towrite = s.length() > avail_width? s.substr(0, avail_width) : s.length() == avail_width? s : s + std::string(avail_width - s.length(), ' '); - cprintf(towrite.c_str()); + cprintf("%s", towrite.c_str()); gotoxy( oldx, oldy ); return true; } -void Menu::draw_select_count( int count ) +void Menu::draw_select_count( int count, bool force ) { - if (!is_set(MF_MULTISELECT)) + if (!force && !is_set(MF_MULTISELECT)) return; - if (selitem_text) + if (f_selitem) { - draw_title_suffix( selitem_text( sel ) ); + draw_title_suffix( f_selitem( &sel ) ); } else { @@ -226,6 +286,13 @@ void Menu::draw_select_count( int count ) } } +std::vector<MenuEntry*> Menu::selected_entries() const +{ + std::vector<MenuEntry*> selection; + get_selected(&selection); + return (selection); +} + void Menu::get_selected( std::vector<MenuEntry*> *selected ) const { selected->clear(); @@ -257,7 +324,7 @@ bool Menu::is_hotkey(int i, int key) items[i]->is_primary_hotkey(key) : items[i]->is_hotkey(key); - return is_set(MF_SELECT_ANY_PAGE)? ishotkey + return !is_set(MF_SELECT_BY_PAGE)? ishotkey : ishotkey && i >= first_entry && i < end; } @@ -371,13 +438,13 @@ void Menu::select_index( int index, int qty ) } } -void Menu::draw_menu( std::vector< MenuEntry* > *selected ) +void Menu::draw_menu() { clrscr(); draw_title(); - draw_select_count( selected->size() ); - y_offset = 2; + draw_select_count( sel.size() ); + y_offset = 1 + !!title; int end = first_entry + pagesize; if (end > (int) items.size()) end = items.size(); @@ -386,11 +453,10 @@ void Menu::draw_menu( std::vector< MenuEntry* > *selected ) { draw_item( i ); } - if (end < (int) items.size()) + if (end < (int) items.size() || is_set(MF_ALWAYS_SHOW_MORE)) { gotoxy( 1, y_offset + pagesize ); - textcolor( LIGHTGREY ); - cprintf("-more-"); + more.display(); } } @@ -401,7 +467,7 @@ void Menu::update_title() gotoxy(x, y); } -int Menu::item_colour(const MenuEntry *entry) const +int Menu::item_colour(int, const MenuEntry *entry) const { int icol = -1; if (highlighter) @@ -415,18 +481,45 @@ void Menu::draw_title() if (title) { gotoxy(1, 1); - textcolor( item_colour(title) ); - cprintf( "%s", title->get_text().c_str() ); + write_title(); } } +void Menu::write_title() +{ + textattr( item_colour(-1, title) ); + cprintf("%s", title->get_text().c_str()); + + const int x = wherex(), y = wherey(); + cprintf("%-*s", get_number_of_cols() - x, ""); + gotoxy(x, y); +} + +bool Menu::in_page(int index) const +{ + return (index >= first_entry && index < first_entry + pagesize); +} + void Menu::draw_item( int index ) const { - if (index < first_entry || index >= first_entry + pagesize) + if (!in_page(index)) return; - gotoxy( 1, y_offset + index - first_entry ); - textcolor( item_colour(items[index]) ); + + draw_item(index, items[index]); +} + +void Menu::draw_item(int index, const MenuEntry *me) const +{ + if (f_drawitem) + (*f_drawitem)(index, me); + else + draw_stock_item(index, me); +} + +void Menu::draw_stock_item(int index, const MenuEntry *me) const +{ + textattr( item_colour(index, items[index]) ); cprintf( "%s", items[index]->get_text().c_str() ); } @@ -481,6 +574,258 @@ bool Menu::line_up() } ///////////////////////////////////////////////////////////////// +// slider_menu + +slider_menu::slider_menu(int fl) + : Menu(fl), less(), starty(1), endy(get_number_of_lines()), + selected(0), search() +{ + less.textcolor(DARKGREY); + less.cprintf("<---- More"); + more.clear(); + more.textcolor(DARKGREY); + more.cprintf("More ---->"); +} + +void slider_menu::set_search(const std::string &s) +{ + search = s; +} + +void slider_menu::set_limits(int y1, int y2) +{ + starty = y1; + endy = y2; +} + +void slider_menu::select_search(const std::string &s) +{ + std::string srch = s; + tolower_string(srch); + + for (int i = 0, size = items.size(); i < size; ++i) + { + std::string text = items[i]->get_text(); + tolower_string(text); + + std::string::size_type found = text.find(srch); + if (found != std::string::npos + && found == text.find_first_not_of(" ")) + { + move_selection(i); + break; + } + } +} + +int slider_menu::post_process(int key) +{ + select_search( search += key ); + return (key); +} + +bool slider_menu::process_key(int key) +{ + // Some of this key processing should really be in a user-passed-in function + // If we ever need to use slider_menu elsewhere, we should factor it out. + if (key == CK_ESCAPE || key == '\t') + { + int old = selected; + selected = -1; + draw_item(old); + sel.clear(); + search.clear(); + lastch = key; + return (false); + } + + if (selected == 0 && + (key == CK_UP || key == CK_PGUP || key == '<' || key == ';') && + Menu::is_set(MF_EASY_EXIT)) + { + int old = selected; + selected = -1; + draw_item(old); + search.clear(); + return (false); + } + + return Menu::process_key(key); +} + +void slider_menu::display() +{ + // We lose two lines for each of the --More prompts + pagesize = endy - starty - 1 - !!title; + selected = -1; + draw_menu(); +} + +std::vector<MenuEntry *> slider_menu::show() +{ + cursor_control coff(false); + + sel.clear(); + + // We lose two lines for each of the --More prompts + pagesize = endy - starty - 1 - !!title; + + if (selected == -1) + selected = 0; + + select_search(search); + do_menu(); + + if (selected >= 0 && selected <= (int) items.size()) + sel.push_back(items[selected]); + return (sel); +} + +const MenuEntry *slider_menu::selected_entry() const +{ + if (selected >= 0 && selected <= (int) items.size()) + return (items[selected]); + + return (NULL); +} + +void slider_menu::draw_stock_item(int index, const MenuEntry *me) const +{ + Menu::draw_stock_item(index, me); + cprintf("%-*s", get_number_of_cols() - wherex() + 1, ""); +} + +int slider_menu::item_colour(int index, const MenuEntry *me) const +{ + int colour = Menu::item_colour(index, me); + if (index == selected && selected != -1) + { +#if defined(WIN32CONSOLE) || defined(DOS) + colour = dos_brand(colour, CHATTR_REVERSE); +#else + colour |= COLFLAG_REVERSE; +#endif + } + return (colour); +} + +void slider_menu::draw_menu() +{ + gotoxy(1, starty); + write_title(); + y_offset = starty + !!title + 1; + + int end = first_entry + pagesize; + if (end > (int) items.size()) end = items.size(); + + // We're using get_number_of_cols() - 1 because we want to avoid line wrap + // on DOS (the conio.h functions go batshit if that happens). + gotoxy(1, y_offset - 1); + if (first_entry > 0) + less.display(); + else + { + textattr(LIGHTGREY); + cprintf("%-*s", get_number_of_cols() - 1, ""); + } + + for (int i = first_entry; i < end; ++i) + draw_item( i ); + + textattr(LIGHTGREY); + for (int i = end; i < first_entry + pagesize; ++i) + { + gotoxy(1, y_offset + i - first_entry); + cprintf("%-*s", get_number_of_cols() - 1, ""); + } + + gotoxy( 1, y_offset + pagesize ); + if (end < (int) items.size() || is_set(MF_ALWAYS_SHOW_MORE)) + more.display(); + else + { + textattr(LIGHTGREY); + cprintf("%-*s", get_number_of_cols() - 1, ""); + } +} + +void slider_menu::select_items(int, int) +{ + // Ignored. +} + +bool slider_menu::is_set(int flag) const +{ + if (flag == MF_EASY_EXIT) + return (false); + return Menu::is_set(flag); +} + +bool slider_menu::fix_entry() +{ + if (selected < 0 || selected >= (int) items.size()) + return (false); + + const int oldfirst = first_entry; + if (selected < first_entry) + first_entry = selected; + else if (selected >= first_entry + pagesize) + { + first_entry = selected - pagesize + 1; + if (first_entry < 0) + first_entry = 0; + } + + return (first_entry != oldfirst); +} + +void slider_menu::new_selection(int nsel) +{ + if (nsel < 0) + nsel = 0; + if (nsel >= (int) items.size()) + nsel = items.size() - 1; + + const int old = selected; + selected = nsel; + if (old != selected && nsel >= first_entry && nsel < first_entry + pagesize) + { + draw_item(old); + draw_item(selected); + } +} + +bool slider_menu::move_selection(int nsel) +{ + new_selection(nsel); + return fix_entry(); +} + +bool slider_menu::page_down() +{ + search.clear(); + return move_selection( selected + pagesize ); +} + +bool slider_menu::page_up() +{ + search.clear(); + return move_selection( selected - pagesize ); +} + +bool slider_menu::line_down() +{ + search.clear(); + return move_selection( selected + 1 ); +} + +bool slider_menu::line_up() +{ + search.clear(); + return move_selection( selected - 1 ); +} + +///////////////////////////////////////////////////////////////// // Menu colouring // @@ -499,3 +844,364 @@ int MenuHighlighter::entry_colour(const MenuEntry *entry) const { return (::menu_colour(entry->get_text())); } + + +//////////////////////////////////////////////////////////////////// +// formatted_string +// + +formatted_string::formatted_string(const std::string &s, bool init_style) + : ops() +{ + if (init_style) + ops.push_back(LIGHTGREY); + ops.push_back(s); +} + +int formatted_string::get_colour(const std::string &tag) +{ + if (tag == "h") + return (YELLOW); + + if (tag == "w") + return (WHITE); + + const int colour = str_to_colour(tag); + return (colour != -1? colour : LIGHTGREY); +} + +formatted_string formatted_string::parse_string( + const std::string &s, + bool eol_ends_format, + bool (*process)(const std::string &tag)) +{ + // FIXME This is a lame mess, just good enough for the task on hand + // (keyboard help). + formatted_string fs; + std::string::size_type tag = std::string::npos; + std::string::size_type length = s.length(); + + std::string currs; + int curr_colour = LIGHTGREY; + bool masked = false; + + for (tag = 0; tag < length; ++tag) + { + bool invert_colour = false; + std::string::size_type endpos = std::string::npos; + + if (s[tag] != '<' || tag >= length - 2) + { + if (!masked) + currs += s[tag]; + continue; + } + + // Is this a << escape? + if (s[tag + 1] == '<') + { + if (!masked) + currs += s[tag]; + tag++; + continue; + } + + endpos = s.find('>', tag + 1); + // No closing >? + if (endpos == std::string::npos) + { + if (!masked) + currs += s[tag]; + continue; + } + + std::string tagtext = s.substr(tag + 1, endpos - tag - 1); + if (tagtext.empty() || tagtext == "/") + { + if (!masked) + currs += s[tag]; + continue; + } + + if (tagtext[0] == '/') + { + invert_colour = true; + tagtext = tagtext.substr(1); + tag++; + } + + if (tagtext[0] == '?') + { + if (tagtext.length() == 1) + masked = false; + else if (process && !process(tagtext.substr(1))) + masked = true; + + tag += tagtext.length() + 1; + continue; + } + + const int new_colour = invert_colour? LIGHTGREY : get_colour(tagtext); + if (new_colour != curr_colour) + { + fs.cprintf(currs); + currs.clear(); + fs.textcolor( curr_colour = new_colour ); + } + tag += tagtext.length() + 1; + } + if (currs.length()) + fs.cprintf(currs); + + if (eol_ends_format && curr_colour != LIGHTGREY) + fs.textcolor(LIGHTGREY); + + return (fs); +} + +formatted_string::operator std::string() const +{ + std::string s; + for (unsigned i = 0, size = ops.size(); i < size; ++i) + { + if (ops[i] == FSOP_TEXT) + s += ops[i].text; + } + return (s); +} + +const formatted_string & +formatted_string::operator += (const formatted_string &other) +{ + ops.insert(ops.end(), other.ops.begin(), other.ops.end()); + return (*this); +} + +std::string::size_type formatted_string::length() const +{ + // Just add up the individual string lengths. + std::string::size_type len = 0; + for (unsigned i = 0, size = ops.size(); i < size; ++i) + if (ops[i] == FSOP_TEXT) + len += ops[i].text.length(); + return (len); +} + +inline void cap(int &i, int max) +{ + if (i < 0 && -i <= max) + i += max; + if (i >= max) + i = max - 1; + if (i < 0) + i = 0; +} + +std::string formatted_string::tostring(int s, int e) const +{ + std::string st; + + int size = ops.size(); + cap(s, size); + cap(e, size); + + for (int i = s; i <= e && i < size; ++i) + { + if (ops[i] == FSOP_TEXT) + st += ops[i].text; + } + return st; +} + +void formatted_string::display(int s, int e) const +{ + int size = ops.size(); + if (!size) + return ; + + cap(s, size); + cap(e, size); + + for (int i = s; i <= e && i < size; ++i) + ops[i].display(); +} + +void formatted_string::gotoxy(int x, int y) +{ + ops.push_back( fs_op(x, y) ); +} + +void formatted_string::movexy(int x, int y) +{ + ops.push_back( fs_op(x, y, true) ); +} + +void formatted_string::textcolor(int color) +{ + ops.push_back(color); +} + +void formatted_string::clear() +{ + ops.clear(); +} + +void formatted_string::cprintf(const char *s, ...) +{ + char buf[1000]; + va_list args; + va_start(args, s); + vsnprintf(buf, sizeof buf, s, args); + va_end(args); + + cprintf(std::string(buf)); +} + +void formatted_string::cprintf(const std::string &s) +{ + ops.push_back(s); +} + +void formatted_string::fs_op::display() const +{ + switch (type) + { + case FSOP_CURSOR: + { + int cx = x, cy = y; + if (relative) + { + cx += wherex(); + cy += wherey(); + } + else + { + if (cx == -1) + cx = wherex(); + if (cy == -1) + cy = wherey(); + } + ::gotoxy(cx, cy); + break; + } + case FSOP_COLOUR: + ::textattr(x); + break; + case FSOP_TEXT: + ::cprintf("%s", text.c_str()); + break; + } +} + +/////////////////////////////////////////////////////////////////////// +// column_composer + +column_composer::column_composer(int cols, ...) + : ncols(cols), pagesize(0), columns() +{ + ASSERT(cols > 0); + + va_list args; + va_start(args, cols); + + columns.push_back( column(1) ); + int lastcol = 1; + for (int i = 1; i < cols; ++i) + { + int nextcol = va_arg(args, int); + ASSERT(nextcol > lastcol); + + lastcol = nextcol; + columns.push_back( column(nextcol) ); + } + + va_end(args); +} + +void column_composer::set_pagesize(int ps) +{ + pagesize = ps; +} + +void column_composer::clear() +{ + flines.clear(); +} + +void column_composer::add_formatted( + int ncol, + const std::string &s, + bool add_separator, + bool eol_ends_format, + bool (*tfilt)(const std::string &), + int margin) +{ + ASSERT(ncol >= 0 && ncol < (int) columns.size()); + + column &col = columns[ncol]; + std::vector<std::string> segs = split_string("\n", s, false, true); + + std::vector<formatted_string> newlines; + // Add a blank line if necessary. Blank lines will not + // be added at page boundaries. + if (add_separator && col.lines && !segs.empty() + && (!pagesize || col.lines % pagesize)) + newlines.push_back(formatted_string()); + + for (unsigned i = 0, size = segs.size(); i < size; ++i) + { + newlines.push_back( + formatted_string::parse_string( + segs[i], + eol_ends_format, + tfilt)); + } + + strip_blank_lines(newlines); + + compose_formatted_column( + newlines, + col.lines, + margin == -1? col.margin : margin); + + col.lines += newlines.size(); + + strip_blank_lines(flines); +} + +std::vector<formatted_string> column_composer::formatted_lines() const +{ + return (flines); +} + +void column_composer::strip_blank_lines(std::vector<formatted_string> &fs) const +{ + for (int i = fs.size() - 1; i >= 0; --i) + { + if (fs[i].length() == 0) + fs.erase( fs.begin() + i ); + else + break; + } +} + +void column_composer::compose_formatted_column( + const std::vector<formatted_string> &lines, + int startline, + int margin) +{ + if (flines.size() < startline + lines.size()) + flines.resize(startline + lines.size()); + + for (unsigned i = 0, size = lines.size(); i < size; ++i) + { + int f = i + startline; + if (margin > 1) + { + int xdelta = margin - flines[f].length() - 1; + if (xdelta > 0) + flines[f].cprintf("%-*s", xdelta, ""); + } + flines[f] += lines[i]; + } +} |