/* htop - Panel.c (C) 2004-2011 Hisham H. Muhammad Released under the GNU GPLv2+, see the COPYING file in the source distribution for its full text. */ #include "config.h" // IWYU pragma: keep #include "Panel.h" #include #include #include #include #include #include #include "CRT.h" #include "ListItem.h" #include "Macros.h" #include "ProvideCurses.h" #include "RichString.h" #include "XUtils.h" const PanelClass Panel_class = { .super = { .extends = Class(Object), .delete = Panel_delete }, .eventHandler = Panel_selectByTyping, }; Panel* Panel_new(int x, int y, int w, int h, const ObjectClass* type, bool owner, FunctionBar* fuBar) { Panel* this; this = xMalloc(sizeof(Panel)); Object_setClass(this, Class(Panel)); Panel_init(this, x, y, w, h, type, owner, fuBar); return this; } void Panel_delete(Object* cast) { Panel* this = (Panel*)cast; Panel_done(this); free(this); } void Panel_init(Panel* this, int x, int y, int w, int h, const ObjectClass* type, bool owner, FunctionBar* fuBar) { this->x = x; this->y = y; this->w = w; this->h = h; this->cursorX = 0; this->cursorY = 0; this->eventHandlerState = NULL; this->items = Vector_new(type, owner, DEFAULT_SIZE); this->scrollV = 0; this->scrollH = 0; this->selected = 0; this->oldSelected = 0; this->selectedLen = 0; this->needsRedraw = true; this->cursorOn = false; this->wasFocus = false; RichString_beginAllocated(this->header); this->defaultBar = fuBar; this->currentBar = fuBar; this->selectionColorId = PANEL_SELECTION_FOCUS; } void Panel_done(Panel* this) { assert (this != NULL); free(this->eventHandlerState); Vector_delete(this->items); FunctionBar_delete(this->defaultBar); RichString_delete(&this->header); } void Panel_setCursorToSelection(Panel* this) { this->cursorY = this->y + this->selected - this->scrollV + 1; this->cursorX = this->x + this->selectedLen - this->scrollH; } void Panel_setSelectionColor(Panel* this, ColorElements colorId) { this->selectionColorId = colorId; } inline void Panel_setHeader(Panel* this, const char* header) { RichString_writeWide(&(this->header), CRT_colors[PANEL_HEADER_FOCUS], header); this->needsRedraw = true; } void Panel_move(Panel* this, int x, int y) { assert (this != NULL); this->x = x; this->y = y; this->needsRedraw = true; } void Panel_resize(Panel* this, int w, int h) { assert (this != NULL); this->w = w; this->h = h; this->needsRedraw = true; } void Panel_prune(Panel* this) { assert (this != NULL); Vector_prune(this->items); this->scrollV = 0; this->selected = 0; this->oldSelected = 0; this->needsRedraw = true; } void Panel_add(Panel* this, Object* o) { assert (this != NULL); Vector_add(this->items, o); this->needsRedraw = true; } void Panel_insert(Panel* this, int i, Object* o) { assert (this != NULL); Vector_insert(this->items, i, o); this->needsRedraw = true; } void Panel_set(Panel* this, int i, Object* o) { assert (this != NULL); Vector_set(this->items, i, o); } Object* Panel_get(Panel* this, int i) { assert (this != NULL); return Vector_get(this->items, i); } Object* Panel_remove(Panel* this, int i) { assert (this != NULL); this->needsRedraw = true; Object* removed = Vector_remove(this->items, i); if (this->selected > 0 && this->selected >= Vector_size(this->items)) { this->selected--; } return removed; } Object* Panel_getSelected(Panel* this) { assert (this != NULL); if (Vector_size(this->items) > 0) { return Vector_get(this->items, this->selected); } else { return NULL; } } void Panel_moveSelectedUp(Panel* this) { assert (this != NULL); Vector_moveUp(this->items, this->selected); if (this->selected > 0) { this->selected--; } } void Panel_moveSelectedDown(Panel* this) { assert (this != NULL); Vector_moveDown(this->items, this->selected); if (this->selected + 1 < Vector_size(this->items)) { this->selected++; } } int Panel_getSelectedIndex(const Panel* this) { assert (this != NULL); return this->selected; } int Panel_size(const Panel* this) { assert (this != NULL); return Vector_size(this->items); } void Panel_setSelected(Panel* this, int selected) { assert (this != NULL); int size = Vector_size(this->items); if (selected >= size) { selected = size - 1; } if (selected < 0) { selected = 0; } this->selected = selected; if (Panel_eventHandlerFn(this)) { Panel_eventHandler(this, EVENT_SET_SELECTED); } } void Panel_splice(Panel* this, Vector* from) { assert (this != NULL); assert (from != NULL); Vector_splice(this->items, from); this->needsRedraw = true; } void Panel_draw(Panel* this, bool force_redraw, bool focus, bool highlightSelected, bool hideFunctionBar) { assert (this != NULL); int size = Vector_size(this->items); int scrollH = this->scrollH; int y = this->y; int x = this->x; int h = this->h; if (hideFunctionBar) h++; const int header_attr = focus ? CRT_colors[PANEL_HEADER_FOCUS] : CRT_colors[PANEL_HEADER_UNFOCUS]; if (force_redraw) { if (Panel_printHeaderFn(this)) Panel_printHeader(this); else RichString_setAttr(&this->header, header_attr); } int headerLen = RichString_sizeVal(this->header); if (headerLen > 0) { attrset(header_attr); mvhline(y, x, ' ', this->w); if (scrollH < headerLen) { RichString_printoffnVal(this->header, y, x, scrollH, MINIMUM(headerLen - scrollH, this->w)); } attrset(CRT_colors[RESET_COLOR]); y++; h--; } // ensure scroll area is on screen if (this->scrollV < 0) { this->scrollV = 0; this->needsRedraw = true; } else if (this->scrollV > size - h) { this->scrollV = MAXIMUM(size - h, 0); this->needsRedraw = true; } // ensure selection is on screen if (this->selected < this->scrollV) { this->scrollV = this->selected; this->needsRedraw = true; } else if (this->selected >= this->scrollV + h) { this->scrollV = this->selected - h + 1; this->needsRedraw = true; } int first = this->scrollV; int upTo = MINIMUM(first + h, size); int selectionColor = focus ? CRT_colors[this->selectionColorId] : CRT_colors[PANEL_SELECTION_UNFOCUS]; if (this->needsRedraw || force_redraw) { int line = 0; for (int i = first; line < h && i < upTo; i++) { const Object* itemObj = Vector_get(this->items, i); RichString_begin(item); Object_display(itemObj, &item); int itemLen = RichString_sizeVal(item); int amt = MINIMUM(itemLen - scrollH, this->w); if (highlightSelected && i == this->selected) { item.highlightAttr = selectionColor; } if (item.highlightAttr) { attrset(item.highlightAttr); RichString_setAttr(&item, item.highlightAttr); this->selectedLen = itemLen; } mvhline(y + line, x, ' ', this->w); if (amt > 0) RichString_printoffnVal(item, y + line, x, scrollH, amt); if (item.highlightAttr) attrset(CRT_colors[RESET_COLOR]); RichString_delete(&item); line++; } while (line < h) { mvhline(y + line, x, ' ', this->w); line++; } } else { const Object* oldObj = Vector_get(this->items, this->oldSelected); RichString_begin(old); Object_display(oldObj, &old); int oldLen = RichString_sizeVal(old); const Object* newObj = Vector_get(this->items, this->selected); RichString_begin(new); Object_display(newObj, &new); int newLen = RichString_sizeVal(new); this->selectedLen = newLen; mvhline(y + this->oldSelected - first, x + 0, ' ', this->w); if (scrollH < oldLen) RichString_printoffnVal(old, y + this->oldSelected - first, x, scrollH, MINIMUM(oldLen - scrollH, this->w)); attrset(selectionColor); mvhline(y + this->selected - first, x + 0, ' ', this->w); RichString_setAttr(&new, selectionColor); if (scrollH < newLen) RichString_printoffnVal(new, y + this->selected - first, x, scrollH, MINIMUM(newLen - scrollH, this->w)); attrset(CRT_colors[RESET_COLOR]); RichString_delete(&new); RichString_delete(&old); } if (focus && (this->needsRedraw || force_redraw || !this->wasFocus)) { if (Panel_drawFunctionBarFn(this)) Panel_drawFunctionBar(this, hideFunctionBar); else if (!hideFunctionBar) FunctionBar_draw(this->currentBar); } this->oldSelected = this->selected; this->wasFocus = focus; this->needsRedraw = false; } static int Panel_headerHeight(const Panel* this) { return RichString_sizeVal(this->header) > 0 ? 1 : 0; } bool Panel_onKey(Panel* this, int key) { assert (this != NULL); const int size = Vector_size(this->items); #define PANEL_SCROLL(amount) \ do { \ this->selected += (amount); \ this->scrollV = CLAMP(this->scrollV + (amount), 0, MAXIMUM(0, (size - this->h - Panel_headerHeight(this)))); \ this->needsRedraw = true; \ } while (0) switch (key) { case KEY_DOWN: case KEY_CTRL('N'): #ifdef KEY_C_DOWN case KEY_C_DOWN: #endif this->selected++; break; case KEY_UP: case KEY_CTRL('P'): #ifdef KEY_C_UP case KEY_C_UP: #endif this->selected--; break; case KEY_LEFT: case KEY_CTRL('B'): if (this->scrollH > 0) { this->scrollH -= MAXIMUM(CRT_scrollHAmount, 0); this->needsRedraw = true; } break; case KEY_RIGHT: case KEY_CTRL('F'): this->scrollH += CRT_scrollHAmount; this->needsRedraw = true; break; case KEY_PPAGE: PANEL_SCROLL(-(this->h - Panel_headerHeight(this))); break; case KEY_NPAGE: PANEL_SCROLL(+(this->h - Panel_headerHeight(this))); break; case KEY_WHEELUP: PANEL_SCROLL(-CRT_scrollWheelVAmount); break; case KEY_WHEELDOWN: PANEL_SCROLL(+CRT_scrollWheelVAmount); break; case KEY_HOME: this->selected = 0; break; case KEY_END: this->selected = size - 1; break; case KEY_CTRL('A'): case '^': this->scrollH = 0; this->needsRedraw = true; break; case KEY_CTRL('E'): case '$': this->scrollH = MAXIMUM(this->selectedLen - this->w, 0); this->needsRedraw = true; break; default: return false; } #undef PANEL_SCROLL // ensure selection within bounds if (this->selected < 0 || size == 0) { this->selected = 0; this->needsRedraw = true; } else if (this->selected >= size) { this->selected = size - 1; this->needsRedraw = true; } return true; } HandlerResult Panel_selectByTyping(Panel* this, int ch) { int size = Panel_size(this); if (ch == '#') return IGNORED; if (!this->eventHandlerState) this->eventHandlerState = xCalloc(100, sizeof(char)); char* buffer = this->eventHandlerState; if (0 < ch && ch < 255 && isgraph((unsigned char)ch)) { int len = strlen(buffer); if (!len) { if ('/' == ch) { ch = '\001'; } else if ('q' == ch) { return BREAK_LOOP; } } else if (1 == len && '\001' == buffer[0]) { len--; } if (len < 99) { buffer[len] = (char) ch; buffer[len + 1] = '\0'; } for (int try = 0; try < 2; try++) { len = strlen(buffer); for (int i = 0; i < size; i++) { const char* cur = ((ListItem*) Panel_get(this, i))->value; while (*cur == ' ') cur++; if (strncasecmp(cur, buffer, len) == 0) { Panel_setSelected(this, i); return HANDLED; } } // if current word did not match, // retry considering the character the start of a new word. buffer[0] = (char) ch; buffer[1] = '\0'; } return HANDLED; } else if (ch != ERR) { buffer[0] = '\0'; } if (ch == 13) { return BREAK_LOOP; } return IGNORED; } int Panel_getCh(Panel* this) { if (this->cursorOn) { move(this->cursorY, this->cursorX); curs_set(1); } else { curs_set(0); } #ifdef HAVE_SET_ESCDELAY set_escdelay(25); #endif return getch(); }