#include "ufed-curses.h" #include #include #include #include #include /* internal members */ static const char* subtitle = NULL; static const sKey* keys = NULL; static sFlag* currentflag = NULL; static sFlag* flags = NULL; static bool withSep = false; // Needed for the scrollbar and its mouse events static int listHeight, barStart, barEnd, dispStart, dispEnd; /* internal prototypes */ static int (*callback)(sFlag**, int); static int (*drawflag)(sFlag*, bool); static void checktermsize(void); static void drawScrollbar(void); static int getListHeight(void); /* internal functions */ /** @brief get the sum of lines the list holds respecting current filtering **/ static int getListHeight() { int result = 0; // Add masked lines if (eMask_masked != e_mask) { if (eState_installed != e_state) result += listStats.lineCountMasked; if (eState_notinstalled != e_state) result += listStats.lineCountMaskedInstalled; } // Add global lines if (eScope_local != e_scope) { if (eState_installed != e_state) result += listStats.lineCountGlobal; if (eState_notinstalled != e_state) result += listStats.lineCountGlobalInstalled; } // Add local lines if (eScope_global != e_scope) { if (eState_installed != e_state) result += listStats.lineCountLocal; if (eState_notinstalled != e_state) result += listStats.lineCountLocalInstalled; } return result; } void initcurses() { setlocale(LC_CTYPE, ""); initscr(); start_color(); cbreak(); noecho(); keypad(stdscr, TRUE); init_pair(1, COLOR_CYAN, COLOR_BLUE); init_pair(2, COLOR_WHITE, COLOR_WHITE); init_pair(3, COLOR_BLACK, COLOR_WHITE); init_pair(4, COLOR_RED, COLOR_WHITE); init_pair(5, COLOR_BLUE, COLOR_WHITE); init_pair(6, COLOR_BLACK, COLOR_CYAN); #ifdef NCURSES_MOUSE_VERSION mousemask(BUTTON1_CLICKED | BUTTON1_DOUBLE_CLICKED | BUTTON1_PRESSED | BUTTON1_RELEASED, NULL); #endif checktermsize(); { eWin w; for(w = (eWin) 0; w != wCount; w++) { window[w].win = newwin(wHeight(w), wWidth(w), wTop(w), wLeft(w)); } } } void cursesdone() { eWin w; for(w = (eWin) 0; w != wCount; w++) delwin(window[w].win); endwin(); } static void checktermsize() { while(wHeight(List) < 1 || wWidth(List) < (minwidth + 10)) { #ifdef KEY_RESIZE clear(); attrset(0); mvaddstr(0, 0, "Your screen is too small. Press Ctrl+C to exit."); while(getch()!=KEY_RESIZE) {} #else ERROR_EXIT(-1, "The following error occurred:\n\"%2\n\"", "Your screen is too small.\n") #endif } } /** @brief redraw Bottom with or without status separators * The window is not fully refreshed! * @param withSep Draw status separators and filter status if true. */ void drawBottom(bool withSep) { WINDOW* w = win(Bottom); int bWidth = wWidth(Bottom); wattrset(w, COLOR_PAIR(2) | A_BOLD); mvwaddch(w, 0, 0, ACS_VLINE); wattrset(w, COLOR_PAIR(3)); waddch(w, ' '); waddch(w, ACS_LLCORNER); whline(w, ACS_HLINE, bWidth - 6); if (withSep) { mvwaddch(w, 0, minwidth + 3, ACS_BTEE); // Before state mvwaddch(w, 0, minwidth + 7, ACS_BTEE); // Between state and scope mvwaddch(w, 0, minwidth + 10, ACS_BTEE); // After scope } mvwaddch(w, 0, bWidth - 3, ACS_LRCORNER); waddch(w, ' '); wattrset(w, COLOR_PAIR(2) | A_BOLD); waddch (w, ACS_VLINE); // Right vline on line 0 waddch (w, ACS_VLINE); // Left vline on line 1 whline (w, ' ', bWidth - 2); // Blank line (filled with keys later) mvwaddch(w, 1, bWidth - 1, ACS_VLINE); // Right vline on line 1 waddch (w, ACS_VLINE); // Left vline on line 2 whline (w, ' ', bWidth - 2); // Blank line (filled with keys later) mvwaddch(w, 2, bWidth - 1, ACS_VLINE); // Right vline on line 2 mvwaddch(w, 3, 0, ACS_LLCORNER); // lower left corner on line 3 whline (w, ACS_HLINE, bWidth - 2); // bottom line mvwaddch(w, 3, bWidth - 1, ACS_LRCORNER); // lower right corner on line 3 if (keys) { const sKey* key = keys; int pos = 2; int row = 0; int len = 0; while (key->key != '\0') { if (row != key->row) { row = key->row; pos = 2; } len = strlen(key->descr); if (pos < (bWidth - 2)) { if (len > (bWidth - 2 - pos)) len = bWidth - 2 - pos; if (key->key > 0) wattrset(w, COLOR_PAIR(6)); else wattrset(w, COLOR_PAIR(3)); mvwaddnstr(w, row + 1, pos, key->descr, len); } pos += len + 1; ++key; } } wnoutrefresh(w); } void drawFlags() { WINDOW* wLst = win(List); int lHeight = wHeight(List); int lWidth = wWidth(List); /* this method must not be called if the current * item is not valid. */ if (!isFlagLegal(currentflag)) ERROR_EXIT(-1, "drawflags() must not be called with a filtered currentflag! (topline %d listline %d)\n", topline, currentflag->listline) sFlag* flag = currentflag; sFlag* last = currentflag; /* lHeight - flagHeight are compared against listline - topline, * because the latter can result in a too large value if a * strong limiting filter (like "masked") has just been turned * off. */ int line = flag->listline - topline; if (line > lHeight) line = lHeight - getFlagHeight(flag); /* move to the top of the displayed list */ while ((flag != flags) && (line > 0)) { flag = flag->prev; if (isFlagLegal(flag)) { line -= getFlagHeight(flag); last = flag; } } /* If the above move ended up with flag == flags * topline and line must be adapted to the last * found not filtered flag. * This can happen if the flag filter is toggled * and the current flag is the first not filtered. */ if (flag == flags) { if (!isFlagLegal(flag)) { flag = last; topline = last->listline; } line = 0; } // The display start line might differ from topline: dispStart = flag->listline; for( ; line < lHeight; ) { flag->currline = line; // drawflag() and maineventloop() need this line += drawflag(flag, flag == currentflag ? TRUE : FALSE); if (line < lHeight) { flag = flag->next; /* Add blank lines if we reached the end of the * flag list, but not the end of the display. */ if(flag == flags) { wattrset(wLst, COLOR_PAIR(3)); while(line < lHeight) { mvwhline(wLst, line, 0, ' ', lWidth); mvwaddch(wLst, line, minwidth, ACS_VLINE); // Before state mvwaddch(wLst, line, minwidth + 4, ACS_VLINE); // Between state and scope mvwaddch(wLst, line, minwidth + 7, ACS_VLINE); // After scope ++line; } } } else dispEnd = flag->listline + flag->ndesc; } wmove(win(Input), 0, strlen(fayt)); wnoutrefresh(wLst); } static void drawScrollbar() { int sHeight = wHeight(Scrollbar); int lHeight = wHeight(List); WINDOW *w = win(Scrollbar); wattrset(w, COLOR_PAIR(3) | A_BOLD); mvwaddch(w, 0, 0, ACS_UARROW); wvline(w, ACS_CKBOARD, sHeight - 3); /* The scrollbar location differs related to the * current filtering of masked flags. */ listHeight = getListHeight(); // Only show a scrollbar if the list is actually longer than can be displayed: if (listHeight > lHeight) { int sbHeight = sHeight - 3; barStart = 1 + (dispStart * sbHeight / bottomline); barEnd = barStart + ((dispEnd - dispStart) * lHeight / bottomline); // Strongly filtered lists scatter much and must be corrected: if (barEnd > sbHeight) { barStart -= barEnd - sbHeight; barEnd -= barEnd - sbHeight; } for ( ; barStart <= barEnd; ++barStart) mvwaddch(w, barStart, 0, ACS_BLOCK); } mvwaddch(w, sHeight - 2, 0, ACS_DARROW); mvwaddch(w, sHeight - 1, 0, ACS_VLINE); wmove(win(Input), 0, strlen(fayt)); wnoutrefresh(w); } /** @brief redraw Input with blank input and current status if @a withSep is true * This function resets the cursor to 0,0. * The window is not fully refreshed! * @param withSep Draw status separators and filter status if true. */ void drawStatus(bool withSep) { WINDOW* w = win(Input); int iWidth = wWidth(Input); wattrset(w, COLOR_PAIR(3)); // Blank the input area mvwhline(w, 0, 0, ' ', withSep ? minwidth : iWidth); if (withSep) { char buf[COLS+1]; // Add Status separators and explenation characters mvwaddch (w, 0, minwidth , ACS_VLINE); // Before state mvwaddstr(w, 0, minwidth + 1, "DPC"); // Default, Profile, Config mvwaddch (w, 0, minwidth + 4, ACS_VLINE); // Between state and scope mvwaddstr(w, 0, minwidth + 5, "Si"); // Scope, installed mvwaddch (w, 0, minwidth + 7, ACS_VLINE); // After scope // Use the unused right side to show the filter status sprintf(buf, "%*s%-6s / %-13s / %-6s] ", max(2, iWidth - 40 - minwidth), " [", eScope_global == e_scope ? "global" : eScope_local == e_scope ? "local" : "all", eState_installed == e_state ? "installed" : eState_notinstalled == e_state ? "not installed" : "all", eMask_masked == e_mask ? "masked" : eMask_unmasked == e_mask ? "normal" : "all"); waddstr(w, buf); } // Reset cursor and apply changes wmove(w, 0, strlen(fayt)); wrefresh(w); } /** @brief redraw Top with or without status separators * The window is not fully refreshed! * @param withSep Draw status separators and filter status if true. */ void drawTop(bool withSep) { WINDOW* w = win(Top); char buf[COLS + 1]; if (ro_mode) { wattrset(w, COLOR_PAIR(4) | A_BOLD | A_REVERSE); sprintf(buf, " READ ONLY %-*.*s READ ONLY ", wWidth(Top) - 22, wWidth(Top) - 22, "Gentoo USE flags editor " PACKAGE_VERSION); } else { wattrset(w, COLOR_PAIR(1) | A_BOLD); sprintf(buf, "%-*.*s", wWidth(Top), wWidth(Top), "Gentoo USE flags editor " PACKAGE_VERSION); } mvwaddstr(w, 0, 0, buf); /// REMOVEME: Stop wasting space //whline(w, ACS_HLINE, wWidth(Top)); wattrset(w, COLOR_PAIR(2) | A_BOLD); mvwaddch(w, 1, 0, ACS_ULCORNER); whline(w, ACS_HLINE, wWidth(Top)-2); mvwaddch(w, 1, wWidth(Top)-1, ACS_URCORNER); waddch(w, ACS_VLINE); if (ro_mode) { wattrset(w, COLOR_PAIR(4) | A_REVERSE); sprintf(buf, " READ ONLY %-*.*s READ ONLY ", wWidth(Top)-24, wWidth(Top)-24, subtitle); } else { wattrset(w, COLOR_PAIR(3)); sprintf(buf, " %-*.*s ", wWidth(Top)-4, wWidth(Top)-4, subtitle); } waddstr(w, buf); wattrset(w, COLOR_PAIR(2) | A_BOLD); waddch(w, ACS_VLINE); /* maybe this should be based on List? * A: Absolutely not, or every line drawing algorithm * had to be rewritten to cover the offsets. - Sven */ waddch(w, ACS_VLINE); wattrset(w, COLOR_PAIR(3)); waddch(w, ' '); waddch(w, ACS_ULCORNER); whline(w, ACS_HLINE, wWidth(Top)-6); if (withSep) { mvwaddch(w, 3, minwidth + 3, ACS_TTEE); // Before state mvwaddch(w, 3, minwidth + 7, ACS_TTEE); // Between state and scope mvwaddch(w, 3, minwidth + 10, ACS_TTEE); // After scope } mvwaddch(w, 3, wWidth(Top)-3, ACS_URCORNER); waddch(w, ' '); wattrset(w, COLOR_PAIR(2) | A_BOLD); waddch(w, ACS_VLINE); wnoutrefresh(w); } /** @brief redraw the whole screen * @param withSep draw status separators if set to true */ void draw(bool withSep) { WINDOW *w = win(Left); wnoutrefresh(stdscr); wattrset(w, COLOR_PAIR(2) | A_BOLD); mvwvline(w, 0, 0, ACS_VLINE, wHeight(Left)); wattrset(w, COLOR_PAIR(3)); mvwvline(w, 0, 1, ' ', wHeight(Left)); mvwvline(w, 0, 2, ACS_VLINE, wHeight(Left)); wnoutrefresh(w); w = win(Right); wattrset(w, COLOR_PAIR(2) | A_BOLD); mvwvline(w, 0, 0, ' ', wHeight(Right)); mvwvline(w, 0, 1, ACS_VLINE, wHeight(Right)); wnoutrefresh(w); drawTop(withSep); drawBottom(withSep); if (flags) { drawFlags(); drawScrollbar(); } drawStatus(withSep); } bool scrollcurrent() { int lsLine = currentflag->listline; int flHeight = getFlagHeight(currentflag); int btLine = lsLine + flHeight; int wdHeight = wHeight(List); if(lsLine < topline) topline = max(lsLine, btLine - wdHeight); else if( btLine > (topline + wdHeight)) topline = min(btLine - wdHeight, lsLine); else return false; drawFlags(); drawScrollbar(); return true; } bool yesno(const char *prompt) { WINDOW* wInp = win(Input); bool doWait = true; bool result = true; drawStatus(true); wattrset(wInp, COLOR_PAIR(4) | A_BOLD); waddstr(wInp, prompt); waddch(wInp, 'Y'); wrefresh(wInp); while(doWait) { switch(getch()) { case '\n': case KEY_ENTER: case 'Y': case 'y': doWait = false; break; case '\033': case 'N': case 'n': drawStatus(true); wrefresh(wInp); result = false; doWait = false; break; #ifdef KEY_RESIZE case KEY_RESIZE: resizeterm(LINES, COLS); checktermsize(); { eWin w; for(w = (eWin) 0; w != wCount; w++) { delwin(window[w].win); window[w].win = newwin(wHeight(w), wWidth(w), wTop(w), wLeft(w)); } } /* this won't work for the help viewer, but it doesn't use yesno() */ topline = 0; scrollcurrent(); draw(true); // Only used outside help view. wattrset(wInp, COLOR_PAIR(4) | A_BOLD); waddstr(wInp, prompt); waddch (wInp, 'Y'); wrefresh(wInp); break; #endif } } return result; } int maineventloop( const char *_subtitle, int(*_callback)(sFlag**, int), int(*_drawflag)(sFlag*, bool), sFlag* _flags, const sKey *_keys, bool _withSep) { int result; { const char *temp = subtitle; subtitle = _subtitle; _subtitle = temp; } { int(*temp)(sFlag**, int) = callback; callback = _callback; _callback = temp; } { int(*temp)(sFlag*, bool) = drawflag; drawflag = _drawflag; _drawflag = temp; } { sFlag* temp = flags; flags = _flags; _flags = temp; } { const sKey *temp = keys; keys = _keys; _keys = temp; } // Save old display position sFlag* oldCurr = currentflag; bool oldSep = withSep; int oldTop = topline; currentflag = flags; topline = 0; withSep = _withSep; // Save filter settings and start with neutral ones eMask oldMask = e_mask; eScope oldScope = e_scope; eState oldState = e_state; e_mask = eMask_unmasked; e_scope = eScope_all; e_state = eState_all; // Draw initial display draw(withSep); for(;;) { int c = getch(); #ifndef NCURSES_MOUSE_VERSION if(c==ERR) continue; #else static int mousekey = ERR; if(c==ERR) { if(errno!=EINTR && mousekey!=ERR) c = mousekey; else continue; } check_key: if(c==KEY_MOUSE) { MEVENT event; if(getmouse(&event)==OK) { if( (mousekey != ERR) && (event.bstate & (BUTTON1_CLICKED | BUTTON1_DOUBLE_CLICKED | BUTTON1_RELEASED)) ) { cbreak(); mousekey = ERR; if(!(event.bstate & (BUTTON1_CLICKED | BUTTON1_DOUBLE_CLICKED))) continue; } if(wmouse_trafo(win(List), &event.y, &event.x, FALSE)) { if(event.bstate & (BUTTON1_CLICKED | BUTTON1_DOUBLE_CLICKED)) { sFlag* flag = currentflag; if(currentflag->currline > event.y) { do flag = flag->prev; while((flag == flags ? flag = NULL, 0 : 1) && flag->currline > event.y); } else if(currentflag->currline + getFlagHeight(currentflag) - 1 < event.y) { do flag = flag->next; while((flag->next == flags ? flag = NULL, 0 : 1) && flag->currline + getFlagHeight(flag) - 1 < event.y); } if(flag == NULL) continue; drawflag(currentflag, FALSE); currentflag = flag; if(event.bstate & BUTTON1_DOUBLE_CLICKED) { result=callback(¤tflag, KEY_MOUSE); if(result>=0) goto exit; } if (scrollcurrent()) drawStatus(withSep); else drawflag(currentflag, TRUE); } } else if(wmouse_trafo(win(Scrollbar), &event.y, &event.x, FALSE)) { // Only do mouse events if there actually is a scrollbar if( (listHeight > wHeight(List)) && (event.bstate & (BUTTON1_PRESSED | BUTTON1_CLICKED | BUTTON1_DOUBLE_CLICKED)) && (event.y < wHeight(Scrollbar)-1) ) { halfdelay(1); #define SIM(key) \ { \ c = KEY_ ## key; \ if(event.bstate & BUTTON1_PRESSED) \ mousekey = c; \ goto check_key; \ } if(event.y == 0) SIM(UP) else if(event.y == wHeight(Scrollbar)-2) SIM(DOWN) else if( (event.y - 1) < barStart) SIM(PPAGE) else if( (event.y - 1) > barEnd) SIM(NPAGE) #undef SIM else if(event.bstate & BUTTON1_PRESSED) { for(;;) { c = getch(); switch(c) { case ERR: continue; case KEY_MOUSE: if(getmouse(&event)==OK) { event.y -= wTop(Scrollbar) + 1; int sbHeight = wHeight(Scrollbar) - 3; if( (event.y >= 0) && (event.y < sbHeight) ) { topline = (event.y * (listHeight - sbHeight + 2) + sbHeight - 1) / sbHeight; while( (currentflag != flags) && (currentflag->prev->listline >= topline) ) currentflag = currentflag->prev; while( (currentflag->next != flags) && (currentflag->listline < topline) ) currentflag = currentflag->next; if( (currentflag->listline + currentflag->ndesc) > (topline + wHeight(List)) ) topline = currentflag->listline + currentflag->ndesc - wHeight(List); drawFlags(); drawScrollbar(); wrefresh(win(List)); } } break; default: goto check_key; } break; } } // End of alternate scrollbar event } // End of having a scrollbar } else if(wmouse_trafo(win(Bottom), &event.y, &event.x, FALSE)) { if( (event.bstate & (BUTTON1_CLICKED | BUTTON1_DOUBLE_CLICKED)) && (event.y >= 1) && (event.y <= 2)) { const sKey* key = keys; int x = event.x; int y = event.y; if((x < 2) || (y < 1) || (y > 2)) continue; x -= 2; --y; // Forward to second row if y is 1 for ( ; y > key->row; key++) ; // Check key for ( ; (key->row == y) && (x >= 0) && (key->key != '\0'); key++) { if ((key->key > 0) && ((size_t)x < key->length) ) { event.x -= x; wattrset(win(Bottom), COLOR_PAIR(6) | A_BOLD | A_REVERSE); mvwaddstr(win(Bottom), event.y, event.x, key->descr); wmove(win(Bottom), event.y, event.x); wrefresh(win(Bottom)); usleep(100000); wattrset(win(Bottom), COLOR_PAIR(6)); waddstr(win(Bottom), key->descr); wnoutrefresh(win(Bottom)); c = key->key; goto check_key; } x -= key->length + 1; } } } } } else #endif { result = callback(¤tflag, c); if(result >= 0) goto exit; switch(c) { case KEY_UP: if(currentflag->currline < 0 ) { --topline; drawFlags(); drawScrollbar(); } else setPrevItem(1, true); break; case KEY_DOWN: if( (currentflag->currline + getFlagHeight(currentflag)) > wHeight(List) ) { ++topline; drawFlags(); drawScrollbar(); } else setNextItem(1, true); break; case KEY_PPAGE: if(currentflag != flags) setPrevItem(wHeight(List), false); break; case KEY_NPAGE: if(currentflag->next != flags) setNextItem(wHeight(List), false); break; case KEY_HOME: if(currentflag != flags) resetDisplay(withSep); break; case KEY_END: if(currentflag->next != flags) { drawflag(currentflag, FALSE); currentflag = flags->prev; while (!isFlagLegal(currentflag)) currentflag = currentflag->prev; scrollcurrent(); drawflag(currentflag, TRUE); } break; #ifdef KEY_RESIZE case KEY_RESIZE: resizeterm(LINES, COLS); checktermsize(); { eWin w; for(w = (eWin) 0; w != wCount; w++) { delwin(window[w].win); window[w].win = newwin(wHeight(w), wWidth(w), wTop(w), wLeft(w)); } } if(result == -1) { topline = 0; scrollcurrent(); } else // This is the result of a resize in // the help screen, it will re-init // the help text lines. flags = currentflag; draw(withSep); break; #endif } } doupdate(); } exit: subtitle = _subtitle; callback = _callback; drawflag = _drawflag; flags = _flags; keys = _keys; // Reset display: currentflag = oldCurr; topline = oldTop; withSep = oldSep; // Revert filters e_mask = oldMask; e_scope = oldScope; e_state = oldState; if(flags != NULL) draw(withSep); return result; } /** @brief Set display to first legal item and redraw * @param withSep draw status separators if set to true */ void resetDisplay(bool withSep) { currentflag = flags; while (!isFlagLegal(currentflag) && (currentflag->next != flags)) currentflag = currentflag->next; topline = currentflag->listline; draw(withSep); } /** @brief set currentflag to the next flag @a count lines away * @param count set how many lines should be skipped * @param strict if set to false, at least one item has to be skipped. * @return true if currentflag was changed, flase otherwise */ bool setNextItem(int count, bool strict) { bool result = true; sFlag* curr = currentflag; sFlag* lastFlag = NULL; int lastTop = 0; int skipped = 0; int oldTop = topline; int fHeight = 0; // It is crucial to start with a not filtered flag: while (!isFlagLegal(curr) && (curr->next != flags)) { topline += curr->ndesc; curr = curr->next; } // Break this if the current item is still filtered if (!isFlagLegal(curr)) { topline = oldTop; return false; } while (result && (skipped < count) && (curr->next != flags)) { lastFlag = curr; lastTop = topline; fHeight = getFlagHeight(curr); skipped += fHeight; topline += curr->ndesc - fHeight; curr = curr->next; // Ensure a not filtered flag to continue while (!isFlagLegal(curr) && (curr->next != flags)) { topline += curr->ndesc; curr = curr->next; } // It is possible to end up with the last flag // which might be filtered: if (curr->next == flags) { if (!isFlagLegal(curr)) { // Revert to last known legal state: curr = lastFlag; topline = lastTop; skipped -= getFlagHeight(curr); } // Did we fail ? if (skipped < count) result = false; } } // End of trying to find a next item if ( (result && strict) || (!strict && skipped) ) { drawflag(currentflag, FALSE); currentflag = curr; if (!scrollcurrent()) drawflag(currentflag, TRUE); result = true; } else { topline = oldTop; result = false; } return result; } /* @brief set currentflag to the previous item @a count lines away * @param count set how many lines should be skipped * @param strict if set to false, at least one item has to be skipped. * @return true if currentflag was changed, flase otherwise */ bool setPrevItem(int count, bool strict) { bool result = true; sFlag* curr = currentflag; sFlag* lastFlag = NULL; int lastTop = 0; int skipped = 0; int oldTop = topline; int fHeight = 0; // It is crucial to start with a not filtered flag: while (!isFlagLegal(curr) && (curr != flags)) { topline -= curr->ndesc; curr = curr->prev; } // Break this if the current item is still filtered if (!isFlagLegal(curr)) { topline = oldTop; return false; } while (result && (skipped < count) && (curr != flags)) { lastFlag = curr; lastTop = topline; curr = curr->prev; // Ensure a not filtered flag to continue while (!isFlagLegal(curr) && (curr != flags)) { topline -= curr->ndesc; curr = curr->prev; } fHeight = getFlagHeight(curr); skipped += fHeight; topline -= curr->ndesc - fHeight; // It is possible to end up with the first flag // which might be filtered: if (curr == flags) { if (!isFlagLegal(curr)) { // Revert to last known legal state: skipped -= getFlagHeight(curr); curr = lastFlag; topline = lastTop; } // Did we fail ? if (skipped < count) result = false; } } // End of trying to find a next item if ( (result && strict) || (!strict && skipped) ) { drawflag(currentflag, FALSE); currentflag = curr; if (!scrollcurrent()) drawflag(currentflag, TRUE); result = true; } else { topline = oldTop; result = false; } return result; }