#include "ufed-curses.h" #include #include #include #include #include #include #include #include #include "ufed-curses-help.h" /* internal members */ static int descriptionleft = 0; static sFlag** faytsave = NULL; static size_t maxDescWidth = 0; static char* lineBuf = NULL; static sFlag* flags = NULL; /* internal prototypes */ static int findFlagStart(sFlag* flag, int* index, sWrap** wrap, int* line); static void free_flags(void); static char getFlagSpecialChar(sFlag* flag, int index); static void printFlagInfo(char* buf, sFlag* flag, int index, bool printFlagName, bool printFlagState); static void setFlagWrapDraw(sFlag* flag, int index, sWrap** wrap, size_t* pos, size_t* len); /* static functions */ static char *ufed_getline(FILE *fp) { static size_t size = LINE_MAX; if (NULL == lineBuf) { lineBuf = malloc(size); if (NULL == lineBuf) ERROR_EXIT(-1, "Can not allocate %lu bytes for line buffer\n", sizeof(char) * size); } if(fgets(lineBuf, size, fp) == NULL) return NULL; else { /* See to configuration bytes if not read already */ if (!configDone) { /* Byte 1: Whether read-only-mode is set or not */ if ( '0' != lineBuf[0] ) ro_mode = true; configDone = true; /* Remove the leading bytes transporting configuration values */ char *oldLine = lineBuf; lineBuf = malloc(size); if (NULL == lineBuf) ERROR_EXIT(-1, "Can not allocate %lu bytes for line buffer\n", sizeof(char) * size); memcpy(lineBuf, oldLine + 1, size - 1); free(oldLine); } /* End of having to read configuration bytes */ char *p = strchr(lineBuf, '\n'); if(p != NULL) { *p = '\0'; return lineBuf; } } for(;;) { char *newLine = realloc(lineBuf, size + size / 2); if(newLine == NULL) ERROR_EXIT(-1, "Can not reallocate %lu bytes for line buffer\n", (size_t)(sizeof(char) * size * 1.5)); lineBuf = newLine; newLine = NULL; if(fgets(lineBuf + size, size / 2, fp) == NULL) return NULL; else { char *p = strchr(lineBuf + size, '\n'); if(p != NULL) { *p = '\0'; return lineBuf; } } size += size/2; } return NULL; // never reached. } static void read_flags(void) { FILE* input = fdopen(3, "r"); int lineNum = 0; char* line = NULL; int ndescr = 0; char endChar = 0; size_t fullWidth = 0; struct { int start, end; } name, desc, desc_alt, pkg, state; if(input == NULL) ERROR_EXIT(-1, "fdopen failed with error %d\n", errno); atexit(&free_flags); for(line = ufed_getline(input); line ; line = ufed_getline(input)) { name.start = name.end = -1; state.start = state.end = -1; if (sscanf(line, "%n%*s%n [%n%*[ +-]%n] %d", &name.start, &name.end, &state.start, &state.end, &ndescr) != 1) ERROR_EXIT(-1, "Flag read failed on line %d:\n\"%s\"\n", lineNum + 1, line); // Check stats if ((state.end - state.start) != 2) ERROR_EXIT(-1, "Illegal flag stats on line %d:\n\"%s\"\n", lineNum + 1, line); // Create a new flag line[name.end] = '\0'; line[state.end] = '\0'; sFlag* newFlag = addFlag(&flags, &line[name.start], lineNum, ndescr, &line[state.start]); /* The minimum width of the left side display is: * Space + Selection + Space + name + Space + Mask brackets/Force plus. * = 1 + 3 + 1 + strlen(name) + 1 + 2 * = strlen(name) + 8 */ if( (name.end - name.start + 8) > minwidth) minwidth = name.end - name.start + 8; /* read description(s) and determine flag status */ for (int i = 0; i < ndescr; ++i) { desc.start = desc.end = -1; desc_alt.start = desc_alt.end = -1; pkg.start = pkg.end = -1; state.start = state.end = -1; line = ufed_getline(input); if (!line) break; if ( (sscanf(line, "\t%n%*[^\t]%n\t%n%*[^\t]%n\t (%n%*[^)]%n) [%n%*[ +-]%n%c", &desc.start, &desc.end, &desc_alt.start, &desc_alt.end, &pkg.start, &pkg.end, &state.start, &state.end, &endChar) != 1) || (']' != endChar) ) ERROR_EXIT(-1, "Description read failed on line %d\n\"%s\"\n", lineNum + 1, line); // Check stats if ((state.end - state.start) != 7) ERROR_EXIT(-1, "Illegal description stats on line %d:\n\"%s\"\n", lineNum + 1, line); // Add description line to flag: line[desc.end] = '\0'; line[desc_alt.end] = '\0'; line[state.end] = '\0'; if ( (pkg.end - pkg.start) > 1) { line[pkg.end] = '\0'; fullWidth = addFlagDesc(newFlag, &line[pkg.start], &line[desc.start], &line[desc_alt.start], &line[state.start]); } else fullWidth = addFlagDesc(newFlag, NULL, &line[desc.start], &line[desc_alt.start], &line[state.start]); // Note new max length if this line is longest: if (fullWidth > maxDescWidth) maxDescWidth = fullWidth; // Advance lineNum ++lineNum; } // loop through description lines // Update flag states and add data to the list stats genFlagStats(newFlag); addLineStats(newFlag, &listStats); } // loop while input given fclose(input); if(flags == NULL) ERROR_EXIT(-1, "Unable to start: %s\n", "No Input!"); // Save the last line, it is needed in several places bottomline = lineNum; } static int drawflag(sFlag* flag, bool highlight) { // Return early if there is nothing to display: if (!isFlagLegal(flag)) return 0; // Basic values for the flag display int idx = 0; // The description index to start with int line = flag->currline; int usedY = 0; // Counts how many lines this flag really needs to display sWrap* wrapPart = NULL; // Wrap part to begin with/draw // Get the starting description/wrapped line of the flag usedY = findFlagStart(flag, &idx, &wrapPart, &line); // findFlagStart returns -1 if the flag is out of the screen if (0 > usedY) return 0; // Window values (aka shortcuts) WINDOW* wLst = win(List); int lHeight = wHeight(List); int lWidth = wWidth(List); // Set up needed buffers char buf[lWidth + 1]; // Buffer for the line to print char desc[maxDescWidth + 1]; // Buffer to assemble the description accoring to e_order and e_desc char special, *pBuf; // force/mask/none character, Helper to fill buf memset(buf, ' ', sizeof(char) * lWidth); memset(desc, ' ', sizeof(char) * maxDescWidth); buf[lWidth] = 0x0; desc[maxDescWidth] = 0x0; // Description and wrapped lines state values bool hasHead = false; // Set to true once the left side (flag name and states) are printed int rightwidth = lWidth - minwidth - 8; // Space on the right to print descriptions size_t length = rightwidth; // Characters to print when not wrapping bool newDesc = true; // Set to fals when wrapped parts advance size_t pos = descriptionleft; // position in desc to start printing on int leftover = 0; // When wrapping lines, this is left on the right // Safety check: Put in blank line if idx ended up too large if (idx >= flag->ndesc) { // This can happen when filters reduce the list too much // so blank lines must be displayed memset(buf, ' ', lWidth - 1); waddstr(wLst, buf); } // print descriptions according to filters while ( (idx < flag->ndesc) && (line < lHeight) ) { // Continue if any of the filters apply: if (newDesc && !isDescLegal(flag, idx)) { ++idx; continue; } // Always start with a blanked buffer memset(buf, ' ', sizeof(char) * lWidth); // Prepare new description or blank on wrapped parts if (newDesc) { special = getFlagSpecialChar(flag, idx); // Always start with a blank description buffer memset(desc, ' ', sizeof(char) * maxDescWidth); // Wrapped and not wrapped lines are unified here // to simplify the usage of different ordering and // stripped descriptions versus original descriptions if (flag->desc[idx].pkg) { if (e_order == eOrder_left) sprintf(desc, "(%s) %s", flag->desc[idx].pkg, e_desc == eDesc_ori ? flag->desc[idx].desc : flag->desc[idx].desc_alt); else sprintf(desc, "%s (%s)", e_desc == eDesc_ori ? flag->desc[idx].desc : flag->desc[idx].desc_alt, flag->desc[idx].pkg); } else sprintf(desc, "%s", flag->desc[idx].desc); } /* --- Preparations done --- */ // 1: Print left side info if (!hasHead || newDesc) // Note: If both are false, the buffer is blanked already printFlagInfo(buf, flag, idx, !hasHead, newDesc); // At this point buf is guaranteed to be filled up to minwidth + 8 // For normal descriptions, pos and length are already set, but // not so for wrapped lines, these must be set for each line: if (eWrap_wrap == e_wrap) setFlagWrapDraw(flag, idx, &wrapPart, &pos, &length); // The right side of buf can be added now: leftover = rightwidth - (int)length - (newDesc ? 0 : 2); pBuf = &buf[minwidth + (newDesc ? 8 : 10)]; sprintf(pBuf, "%-*.*s", (int)length, (int)length, strlen(desc) > pos ? &desc[pos] : " "); // Note: Follow up lines of wrapped descriptions are indented by 2 // Leftover characters on the right must be blanked: if (leftover > 0) sprintf(pBuf + length, "%-*s", leftover, " "); /* Set correct color set according to highlighting and status*/ if(highlight) wattrset(wLst, COLOR_PAIR(3) | A_BOLD | A_REVERSE); else wattrset(wLst, COLOR_PAIR(3)); // Put the line on the screen mvwaddstr(wLst, line, 0, buf); 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 // Add (default) selection if this is the header line if (!hasHead) { hasHead = true; if (flag->globalForced) { if(highlight) wattrset(wLst, COLOR_PAIR(5) | A_REVERSE); else wattrset(wLst, COLOR_PAIR(5) | A_BOLD); mvwaddch(wLst, line, 2, '+'); } else if (flag->globalMasked) { if(highlight) wattrset(wLst, COLOR_PAIR(4) | A_REVERSE); else wattrset(wLst, COLOR_PAIR(4) | A_BOLD); mvwaddch(wLst, line, 2, '-'); } else if (' ' == flag->stateConf) mvwaddch(wLst, line, 2, flag->stateDefault); else mvwaddch(wLst, line, 2, flag->stateConf); } // Add [D]efault column content if ('f' == special) { if(highlight) wattrset(wLst, COLOR_PAIR(5) | A_REVERSE); else wattrset(wLst, COLOR_PAIR(5) | A_BOLD); mvwaddch(wLst, line, minwidth + 1, special); } else if ('m' == special) { if(highlight) wattrset(wLst, COLOR_PAIR(4) | A_REVERSE); else wattrset(wLst, COLOR_PAIR(4) | A_BOLD); mvwaddch(wLst, line, minwidth + 1, special); } else { if (' ' == flag->desc[idx].stateDefault) mvwaddch(wLst, line, minwidth + 1, flag->stateDefault); else mvwaddch(wLst, line, minwidth + 1, flag->desc[idx].stateDefault); } // Advance counters and possibly description index ++line; ++usedY; // When wrapping the wrapPart must be advanced and checked if (eWrap_wrap == e_wrap) { wrapPart = wrapPart ? wrapPart->next : NULL; if (wrapPart) newDesc = false; else newDesc = true; } if (newDesc) ++idx; } // end of looping flag descriptions if(highlight) wmove(win(List), max(flag->currline, 0), 2); wnoutrefresh(win(List)); return usedY; } static int callback(sFlag** curr, int key) { WINDOW* wInp = win(Input); WINDOW* wLst = win(List); size_t fLen = 0; if ( fayt[0] && (key != KEY_BACKSPACE) && (key != KEY_DC) && (key != 0177) && ( (key == ' ') || (key != (unsigned char)key) || !isprint(key)) ) { fayt[0] = '\0'; drawStatus(true); wrefresh(wInp); } else wmove(wInp, 0, strlen(fayt)); // Reset possible side scrolling of the current flags description first if(descriptionleft && (key != KEY_LEFT) && (key != KEY_RIGHT) ) { descriptionleft = 0; drawflag(*curr, TRUE); } switch(key) { case KEY_DC: case 0177: case KEY_BACKSPACE: fLen = strlen(fayt); if(0 == fLen) break; fayt[--fLen] = '\0'; drawflag(*curr, FALSE); *curr = faytsave[fLen]; if (!scrollcurrent()) drawflag(*curr, TRUE); wattrset(wInp, COLOR_PAIR(5) | A_BOLD); mvwaddstr(wInp, 0, 0, fayt); whline(wInp, ' ', 2); if(fLen == 0) wmove(wLst, (*curr)->currline, 2); wnoutrefresh(wLst); wrefresh(wInp); break; case '\n': case KEY_ENTER: if (ro_mode) { if (yesno("Exit? (Y/N) ")) return 0; } else if (yesno("Save and exit? (Y/N) ")) return 0; break; case '\033': if (ro_mode) { if (yesno("Exit? (Y/N) ")) return 0; } else if (yesno("Cancel? (Y/N) ")) return 1; break; case ' ': // Masked flags can be turned off, nothing else if ( (*curr)->globalMasked || (*curr)->globalForced ) { if (' ' != (*curr)->stateConf) (*curr)->stateConf = ' '; } else { switch ((*curr)->stateConf) { case '+': (*curr)->stateConf = '-'; break; case '-': (*curr)->stateConf = ' '; break; default: (*curr)->stateConf = '+'; break; } } if (*curr != flags) { drawflag(*curr, TRUE); wmove(wLst, (*curr)->currline, 2); wrefresh(wLst); } else drawFlags(); break; case KEY_LEFT: if (eWrap_normal == e_wrap) { if(descriptionleft > 0) descriptionleft -= min(descriptionleft, (wWidth(List) - minwidth) * 2 / 3); drawflag(*curr, TRUE); wmove(wLst, (*curr)->currline, 2); wrefresh(wLst); } break; case KEY_RIGHT: if (eWrap_normal == e_wrap) { descriptionleft += (wWidth(List) - minwidth) * 2 / 3; drawflag(*curr, TRUE); wmove(wLst, (*curr)->currline, 2); wrefresh(wLst); } break; case KEY_F(5): if (eScope_local == e_scope) e_scope = eScope_all; else ++e_scope; if ( !isFlagLegal(*curr) && !setNextItem(0, true) && !setPrevItem(0, true) ) resetDisplay(true); else draw(true); break; case KEY_F(6): if (eState_notinstalled == e_state) e_state = eState_all; else ++e_state; if ( !isFlagLegal(*curr) && !setNextItem(0, true) && !setPrevItem(0, true) ) resetDisplay(true); else draw(true); break; case KEY_F(7): if (eMask_both == e_mask) e_mask = eMask_unmasked; else ++e_mask; if ( !isFlagLegal(*curr) && !setNextItem(0, true) && !setPrevItem(0, true) ) resetDisplay(true); else draw(true); break; case KEY_F(9): if (eOrder_left == e_order) e_order = eOrder_right; else e_order = eOrder_left; draw(true); break; case KEY_F(10): if (eDesc_ori == e_desc) e_desc = eDesc_alt; else e_desc = eDesc_ori; draw(true); break; case KEY_F(11): if (eWrap_normal == e_wrap) { e_wrap = eWrap_wrap; descriptionleft = 0; } else e_wrap = eWrap_normal; draw(true); break; #ifdef NCURSES_MOUSE_VERSION case KEY_MOUSE: // Masked flags can be turned off, nothing else if ( (*curr)->globalMasked || (*curr)->globalForced ) { if (' ' != (*curr)->stateConf) (*curr)->stateConf = ' '; } else { switch ((*curr)->stateConf) { case '+': (*curr)->stateConf = '-'; break; case '-': (*curr)->stateConf = ' '; break; default: (*curr)->stateConf = '+'; break; } } if (*curr != flags) { drawflag(*curr, TRUE); wmove(wLst, (*curr)->currline, 2); wrefresh(wLst); } else { drawFlags(); wmove(wInp, 0, strlen(fayt)); } break; #endif case '?': help(); break; default: if( (key == (unsigned char) key) && isprint(key)) { sFlag* flag = *curr; fLen = strlen(fayt); if(fLen && strncasecmp(flag->name, fayt, fLen)) --fLen; fayt[fLen] = (char) key; faytsave[fLen] = *curr; fayt[++fLen] = '\0'; wmove(wInp, 0, fLen); /* if the current flag already matches the input string, * then update the input area only. */ if(!strncasecmp(flag->name, fayt, fLen)) { wattrset(wInp, COLOR_PAIR(5) | A_BOLD); mvwaddstr(wInp, 0, 0, fayt); wrefresh(wInp); } /* if the current flag does not match, search one that does. */ else { do flag = flag->next; while( (flag != *curr) && ( ( strncasecmp(flag->name, fayt, fLen) || !isFlagLegal(flag)) ) ); /* if there was no match (or the match is filtered), * update the input area to show that there is no match */ if (flag == *curr) { wattrset(wInp, COLOR_PAIR(4) | A_BOLD | A_REVERSE); mvwaddstr(wInp, 0, 0, fayt); wmove(wInp, 0, fLen - 1); wrefresh(wInp); } else { drawflag(*curr, FALSE); *curr = flag; if (!scrollcurrent()) drawflag(*curr, TRUE); wattrset(wInp, COLOR_PAIR(5) | A_BOLD); mvwaddstr(wInp, 0, 0, fayt); wmove(wInp, 0, fLen); wrefresh(wInp); } } } break; } return -1; } /** @brief find the first description/wrapped part drawn on line 0. * * Determine with which description to start. * Overly long description lists might not fit on one screen, * and therefore must be scrolled instead of the flags * themselves. * If descriptions are wrapped, any description must be held * until wrapPart is either NULL, or a part on the screen is * reached. **/ static int findFlagStart(sFlag* flag, int* index, sWrap** wrap, int* line) { int usedLines = 0; int flagHeight = getFlagHeight(flag); // Will recalculate wrap parts if needed sWrap* wrapPart = NULL; if (*line < 0) { // Only do anything if the flag can reach the screen: if (-(*line) >= flagHeight) return -1; // Now loop until the screen is entered (line == 0) while (*line < 0) { if (isDescLegal(flag, *index)) { if (eWrap_normal == e_wrap) { ++(*line); ++(*index); ++usedLines; } else { /* With wrapped descriptions there are two possible * situations: * a) The list of wrapped lines is shorter than the * lines to be skipped to get this description on * the screen. In this case the full description * can be fast forwareded. * b) The number of lines above the screen is less * than the number of wrapped parts. In this case * the first on screen part must be found. */ int wrapCount = flag->desc[*index].wrapCount; wrapPart = flag->desc[*index].wrap; if (wrapPart && wrapCount && (-(*line) < wrapCount)) { // Situation b) This description enters screen while (wrapPart && (*line < 0)) { ++(*line); ++usedLines; wrapPart = wrapPart->next; } } else { // Situation a) Fast forward *line += wrapCount; usedLines += wrapCount; ++(*index); } } // End of handling wrapped lines } // End of having a legal flag else ++(*index); } // end of moving to line 0 // Write back wrapPart: *wrap = wrapPart; } return usedLines; } static void free_flags(void) { sFlag* flag = flags->prev; // Clear all flags while (flags) { if (flag) destroyFlag(&flags, &flag); else destroyFlag(&flags, &flags); flag = flags ? flags->prev ? flags->prev : flags : NULL; } // Clear line buffer if (lineBuf) free(lineBuf); } static char getFlagSpecialChar(sFlag* flag, int index) { // Return special character if needed: if (isDescForced(flag, index)) return 'f'; else if (isDescMasked(flag, index)) return 'm'; return ' '; } static void printFlagInfo(char* buf, sFlag* flag, int index, bool printFlagName, bool printFlagState) { if (printFlagName) { sprintf(buf, " %c%c%c %s%s%s%-*s ", /* State of selection */ flag->stateConf == ' ' ? '(' : '[', ' ', // Filled in later flag->stateConf == ' ' ? ')' : ']', /* name */ flag->globalForced ? "(" : flag->globalMasked ? "(-" : "", flag->name, (flag->globalForced || flag->globalMasked) ? ")" : "", /* distance */ (int)(minwidth - (flag->globalForced ? 3 : flag->globalMasked ? 2 : 5) - strlen(flag->name)), " "); buf[minwidth] = ' '; // No automatic \0, please! } if (printFlagState) { /* Display flag state * The order in which the states are to be displayed is: * 1. [D]efaults (make.defaults, IUSE, package.mask, package.force) * Note: Filled in later * 2. [P]rofile package.use files * 3. [C]onfiguration (make.conf, users package.use) * 4. global/local * 5. installed/not installed */ sprintf(buf + minwidth, " %c%c %c%c ", flag->desc[index].statePackage, ' ' == flag->desc[index].statePkgUse ? flag->stateConf : flag->desc[index].statePkgUse, flag->desc[index].isGlobal ? ' ' : 'L', flag->desc[index].isInstalled ? 'i' : ' '); buf[minwidth + 8] = ' '; // No automatic \0, please! } } static void setFlagWrapDraw(sFlag* flag, int index, sWrap** wrap, size_t* pos, size_t* len) { sWrap* wrapPart = *wrap; if (NULL == wrapPart) wrapPart = flag->desc[index].wrap; // The length and position can be written back already if (wrapPart) { *pos = wrapPart->pos; *len = wrapPart->len; } else { *pos = 0; *len = 0; } // Write back wrap part pointer *wrap = wrapPart; } int main(void) { int result = EXIT_SUCCESS; const char subtitle_ro[] = "USE flags can be browsed, but changes will NOT be saved!"; const char subtitle_rw[] = "Select desired USE flags from the list below:"; read_flags(); fayt = (char*) calloc(minwidth, sizeof(*fayt)); faytsave = (sFlag**)calloc(minwidth, sizeof(*faytsave)); if(fayt==NULL || faytsave==NULL) ERROR_EXIT(-1, "Unable to allocate %lu bytes for search buffer.\n", (minwidth * sizeof(*fayt)) + (minwidth * sizeof(*faytsave))); fayt[0] = '\0'; initcurses(); /* Some notes on the keys: * - The key '\0' (or simply 0) stops the processing of the * array. This means, templates or future keys can be added * beforehand behind a key=0 line - they will never be * displayed. * - key < 0 will show the key name only. This is useful for * adding spaces or titles. * - The texts are dynamic. As a smaller display shows only * a limited amount of characters, texts should be crafted to * start with their core information. * Bad : "Show only global" => "Show only g" => "Show o" * Good: "Global flags" => "Global flag" => "Global" */ sKey keys[] = { /* Row 0 left - General keys */ MAKE_KEY('?', "?:", "Help", "", "", NULL, 0), MAKE_KEY('\n', "Enter:", "Save", "Exit", "", (int*)&ro_mode, 0), MAKE_KEY('\033', "Esc:", "Cancel", "Exit", "", (int*)&ro_mode, 0), /* Row 0 right - Display style (description) */ MAKE_KEY(-1, " ", "", "", "", NULL, 0), MAKE_KEY(KEY_F( 9), "F9:", "Pkg right", "Pkg left", "", (int*)&e_order, 0), MAKE_KEY(KEY_F(10), "F10:", "Strip desc", "Full desc", "", (int*)&e_desc, 0), MAKE_KEY(KEY_F(11), "F11:", "Wrap desc", "Unwrap desc", "", (int*)&e_wrap, 0), /* Row 1 - Filter settings */ MAKE_KEY(-1, "Filter: ", "", "", "", NULL, 1), MAKE_KEY(KEY_F( 5), "F5:", "Global only", "Local only", "Both Scopes", (int*)&e_scope, 1), MAKE_KEY(KEY_F( 6), "F6:", "Inst pkgs", "Not inst pkgs", "All pkgs", (int*)&e_state, 1), MAKE_KEY(KEY_F( 7), "F7:", "Masked only", "Both states", "Unmasked only", (int*)&e_mask, 1), MAKE_KEY(0, "", "", "", "", NULL, 0), /* processing stops here (row _MUST_ be 0 here!) */ /* future keys, that are planned */ MAKE_KEY(KEY_F( 8), "F8:", "Unknown flags", "Known flags", "all", NULL, 1) }; result = maineventloop(ro_mode ? subtitle_ro : subtitle_rw, &callback, &drawflag, flags, keys, true); cursesdone(); if(0 == result) { FILE *output = fdopen(4, "w"); sFlag *flag = flags; do { switch(flag->stateConf) { case '+': fprintf(output, "%s\n", flag->name); break; case '-': fprintf(output, "-%s\n", flag->name); break; } flag = flag->next; } while(flag != flags); fclose(output); } if (fayt) free(fayt); if (faytsave) free(faytsave); return result; }