1 /*
2 htop - Panel.c
3 (C) 2004-2011 Hisham H. Muhammad
4 Released under the GNU GPLv2+, see the COPYING file
5 in the source distribution for its full text.
6 */
7 
8 #include "Panel.h"
9 
10 #include <assert.h>
11 #include <ctype.h>
12 #include <stdbool.h>
13 #include <stdlib.h>
14 #include <string.h>
15 #include <strings.h>
16 
17 #include "CRT.h"
18 #include "ListItem.h"
19 #include "Macros.h"
20 #include "ProvideCurses.h"
21 #include "RichString.h"
22 #include "XUtils.h"
23 
24 
25 const PanelClass Panel_class = {
26    .super = {
27       .extends = Class(Object),
28       .delete = Panel_delete
29    },
30    .eventHandler = Panel_selectByTyping,
31 };
32 
Panel_new(int x,int y,int w,int h,const ObjectClass * type,bool owner,FunctionBar * fuBar)33 Panel* Panel_new(int x, int y, int w, int h, const ObjectClass* type, bool owner, FunctionBar* fuBar) {
34    Panel* this;
35    this = xMalloc(sizeof(Panel));
36    Object_setClass(this, Class(Panel));
37    Panel_init(this, x, y, w, h, type, owner, fuBar);
38    return this;
39 }
40 
Panel_delete(Object * cast)41 void Panel_delete(Object* cast) {
42    Panel* this = (Panel*)cast;
43    Panel_done(this);
44    free(this);
45 }
46 
Panel_init(Panel * this,int x,int y,int w,int h,const ObjectClass * type,bool owner,FunctionBar * fuBar)47 void Panel_init(Panel* this, int x, int y, int w, int h, const ObjectClass* type, bool owner, FunctionBar* fuBar) {
48    this->x = x;
49    this->y = y;
50    this->w = w;
51    this->h = h;
52    this->eventHandlerState = NULL;
53    this->items = Vector_new(type, owner, DEFAULT_SIZE);
54    this->scrollV = 0;
55    this->scrollH = 0;
56    this->selected = 0;
57    this->oldSelected = 0;
58    this->selectedLen = 0;
59    this->needsRedraw = true;
60    this->wasFocus = false;
61    RichString_beginAllocated(this->header);
62    this->defaultBar = fuBar;
63    this->currentBar = fuBar;
64    this->selectionColorId = PANEL_SELECTION_FOCUS;
65 }
66 
Panel_done(Panel * this)67 void Panel_done(Panel* this) {
68    assert (this != NULL);
69    free(this->eventHandlerState);
70    Vector_delete(this->items);
71    FunctionBar_delete(this->defaultBar);
72    RichString_delete(&this->header);
73 }
74 
Panel_setSelectionColor(Panel * this,ColorElements colorId)75 void Panel_setSelectionColor(Panel* this, ColorElements colorId) {
76    this->selectionColorId = colorId;
77 }
78 
Panel_setHeader(Panel * this,const char * header)79 inline void Panel_setHeader(Panel* this, const char* header) {
80    RichString_writeWide(&(this->header), CRT_colors[PANEL_HEADER_FOCUS], header);
81    this->needsRedraw = true;
82 }
83 
Panel_move(Panel * this,int x,int y)84 void Panel_move(Panel* this, int x, int y) {
85    assert (this != NULL);
86 
87    this->x = x;
88    this->y = y;
89    this->needsRedraw = true;
90 }
91 
Panel_resize(Panel * this,int w,int h)92 void Panel_resize(Panel* this, int w, int h) {
93    assert (this != NULL);
94 
95    this->w = w;
96    this->h = h;
97    this->needsRedraw = true;
98 }
99 
Panel_prune(Panel * this)100 void Panel_prune(Panel* this) {
101    assert (this != NULL);
102 
103    Vector_prune(this->items);
104    this->scrollV = 0;
105    this->selected = 0;
106    this->oldSelected = 0;
107    this->needsRedraw = true;
108 }
109 
Panel_add(Panel * this,Object * o)110 void Panel_add(Panel* this, Object* o) {
111    assert (this != NULL);
112 
113    Vector_add(this->items, o);
114    this->needsRedraw = true;
115 }
116 
Panel_insert(Panel * this,int i,Object * o)117 void Panel_insert(Panel* this, int i, Object* o) {
118    assert (this != NULL);
119 
120    Vector_insert(this->items, i, o);
121    this->needsRedraw = true;
122 }
123 
Panel_set(Panel * this,int i,Object * o)124 void Panel_set(Panel* this, int i, Object* o) {
125    assert (this != NULL);
126 
127    Vector_set(this->items, i, o);
128 }
129 
Panel_get(Panel * this,int i)130 Object* Panel_get(Panel* this, int i) {
131    assert (this != NULL);
132 
133    return Vector_get(this->items, i);
134 }
135 
Panel_remove(Panel * this,int i)136 Object* Panel_remove(Panel* this, int i) {
137    assert (this != NULL);
138 
139    this->needsRedraw = true;
140    Object* removed = Vector_remove(this->items, i);
141    if (this->selected > 0 && this->selected >= Vector_size(this->items)) {
142       this->selected--;
143    }
144 
145    return removed;
146 }
147 
Panel_getSelected(Panel * this)148 Object* Panel_getSelected(Panel* this) {
149    assert (this != NULL);
150    if (Vector_size(this->items) > 0) {
151       return Vector_get(this->items, this->selected);
152    } else {
153       return NULL;
154    }
155 }
156 
Panel_moveSelectedUp(Panel * this)157 void Panel_moveSelectedUp(Panel* this) {
158    assert (this != NULL);
159 
160    Vector_moveUp(this->items, this->selected);
161    if (this->selected > 0) {
162       this->selected--;
163    }
164 }
165 
Panel_moveSelectedDown(Panel * this)166 void Panel_moveSelectedDown(Panel* this) {
167    assert (this != NULL);
168 
169    Vector_moveDown(this->items, this->selected);
170    if (this->selected + 1 < Vector_size(this->items)) {
171       this->selected++;
172    }
173 }
174 
Panel_getSelectedIndex(const Panel * this)175 int Panel_getSelectedIndex(const Panel* this) {
176    assert (this != NULL);
177 
178    return this->selected;
179 }
180 
Panel_size(const Panel * this)181 int Panel_size(const Panel* this) {
182    assert (this != NULL);
183 
184    return Vector_size(this->items);
185 }
186 
Panel_setSelected(Panel * this,int selected)187 void Panel_setSelected(Panel* this, int selected) {
188    assert (this != NULL);
189 
190    int size = Vector_size(this->items);
191    if (selected >= size) {
192       selected = size - 1;
193    }
194    if (selected < 0) {
195       selected = 0;
196    }
197    this->selected = selected;
198    if (Panel_eventHandlerFn(this)) {
199       Panel_eventHandler(this, EVENT_SET_SELECTED);
200    }
201 }
202 
Panel_splice(Panel * this,Vector * from)203 void Panel_splice(Panel* this, Vector* from) {
204    assert (this != NULL);
205    assert (from != NULL);
206 
207    Vector_splice(this->items, from);
208    this->needsRedraw = true;
209 }
210 
Panel_draw(Panel * this,bool force_redraw,bool focus,bool highlightSelected,bool hideFunctionBar)211 void Panel_draw(Panel* this, bool force_redraw, bool focus, bool highlightSelected, bool hideFunctionBar) {
212    assert (this != NULL);
213 
214    int size = Vector_size(this->items);
215    int scrollH = this->scrollH;
216    int y = this->y;
217    int x = this->x;
218    int h = this->h;
219 
220    if (hideFunctionBar)
221       h++;
222 
223    const int header_attr = focus
224                          ? CRT_colors[PANEL_HEADER_FOCUS]
225                          : CRT_colors[PANEL_HEADER_UNFOCUS];
226    if (force_redraw) {
227       if (Panel_printHeaderFn(this))
228          Panel_printHeader(this);
229       else
230          RichString_setAttr(&this->header, header_attr);
231    }
232    int headerLen = RichString_sizeVal(this->header);
233    if (headerLen > 0) {
234       attrset(header_attr);
235       mvhline(y, x, ' ', this->w);
236       if (scrollH < headerLen) {
237          RichString_printoffnVal(this->header, y, x, scrollH,
238             MINIMUM(headerLen - scrollH, this->w));
239       }
240       attrset(CRT_colors[RESET_COLOR]);
241       y++;
242       h--;
243    }
244 
245    // ensure scroll area is on screen
246    if (this->scrollV < 0) {
247       this->scrollV = 0;
248       this->needsRedraw = true;
249    } else if (this->scrollV > size - h) {
250       this->scrollV = MAXIMUM(size - h, 0);
251       this->needsRedraw = true;
252    }
253    // ensure selection is on screen
254    if (this->selected < this->scrollV) {
255       this->scrollV = this->selected;
256       this->needsRedraw = true;
257    } else if (this->selected >= this->scrollV + h) {
258       this->scrollV = this->selected - h + 1;
259       this->needsRedraw = true;
260    }
261 
262    int first = this->scrollV;
263    int upTo = MINIMUM(first + h, size);
264 
265    int selectionColor = focus
266                       ? CRT_colors[this->selectionColorId]
267                       : CRT_colors[PANEL_SELECTION_UNFOCUS];
268 
269    if (this->needsRedraw || force_redraw) {
270       int line = 0;
271       for (int i = first; line < h && i < upTo; i++) {
272          const Object* itemObj = Vector_get(this->items, i);
273          RichString_begin(item);
274          Object_display(itemObj, &item);
275          int itemLen = RichString_sizeVal(item);
276          int amt = MINIMUM(itemLen - scrollH, this->w);
277          if (highlightSelected && i == this->selected) {
278             item.highlightAttr = selectionColor;
279          }
280          if (item.highlightAttr) {
281             attrset(item.highlightAttr);
282             RichString_setAttr(&item, item.highlightAttr);
283             this->selectedLen = itemLen;
284          }
285          mvhline(y + line, x, ' ', this->w);
286          if (amt > 0)
287             RichString_printoffnVal(item, y + line, x, scrollH, amt);
288          if (item.highlightAttr)
289             attrset(CRT_colors[RESET_COLOR]);
290          RichString_delete(&item);
291          line++;
292       }
293       while (line < h) {
294          mvhline(y + line, x, ' ', this->w);
295          line++;
296       }
297 
298    } else {
299       const Object* oldObj = Vector_get(this->items, this->oldSelected);
300       RichString_begin(old);
301       Object_display(oldObj, &old);
302       int oldLen = RichString_sizeVal(old);
303       const Object* newObj = Vector_get(this->items, this->selected);
304       RichString_begin(new);
305       Object_display(newObj, &new);
306       int newLen = RichString_sizeVal(new);
307       this->selectedLen = newLen;
308       mvhline(y + this->oldSelected - first, x + 0, ' ', this->w);
309       if (scrollH < oldLen)
310          RichString_printoffnVal(old, y + this->oldSelected - first, x,
311             scrollH, MINIMUM(oldLen - scrollH, this->w));
312       attrset(selectionColor);
313       mvhline(y + this->selected - first, x + 0, ' ', this->w);
314       RichString_setAttr(&new, selectionColor);
315       if (scrollH < newLen)
316          RichString_printoffnVal(new, y + this->selected - first, x,
317             scrollH, MINIMUM(newLen - scrollH, this->w));
318       attrset(CRT_colors[RESET_COLOR]);
319       RichString_delete(&new);
320       RichString_delete(&old);
321    }
322 
323    if (focus && (this->needsRedraw || force_redraw || !this->wasFocus)) {
324       if (Panel_drawFunctionBarFn(this))
325          Panel_drawFunctionBar(this, hideFunctionBar);
326       else if (!hideFunctionBar)
327          FunctionBar_draw(this->currentBar);
328    }
329 
330    this->oldSelected = this->selected;
331    this->wasFocus = focus;
332    this->needsRedraw = false;
333    move(0, 0);
334 }
335 
Panel_headerHeight(const Panel * this)336 static int Panel_headerHeight(const Panel* this) {
337    return RichString_sizeVal(this->header) > 0 ? 1 : 0;
338 }
339 
Panel_onKey(Panel * this,int key)340 bool Panel_onKey(Panel* this, int key) {
341    assert (this != NULL);
342 
343    const int size = Vector_size(this->items);
344 
345    #define PANEL_SCROLL(amount)                                                                                     \
346    do {                                                                                                             \
347       this->selected += (amount);                                                                                   \
348       this->scrollV = CLAMP(this->scrollV + (amount), 0, MAXIMUM(0, (size - this->h - Panel_headerHeight(this))));  \
349       this->needsRedraw = true;                                                                                     \
350    } while (0)
351 
352    switch (key) {
353    case KEY_DOWN:
354    case KEY_CTRL('N'):
355    #ifdef KEY_C_DOWN
356    case KEY_C_DOWN:
357    #endif
358       this->selected++;
359       break;
360 
361    case KEY_UP:
362    case KEY_CTRL('P'):
363    #ifdef KEY_C_UP
364    case KEY_C_UP:
365    #endif
366       this->selected--;
367       break;
368 
369    case KEY_LEFT:
370    case KEY_CTRL('B'):
371       if (this->scrollH > 0) {
372          this->scrollH -= MAXIMUM(CRT_scrollHAmount, 0);
373          this->needsRedraw = true;
374       }
375       break;
376 
377    case KEY_RIGHT:
378    case KEY_CTRL('F'):
379       this->scrollH += CRT_scrollHAmount;
380       this->needsRedraw = true;
381       break;
382 
383    case KEY_PPAGE:
384       PANEL_SCROLL(-(this->h - Panel_headerHeight(this)));
385       break;
386 
387    case KEY_NPAGE:
388       PANEL_SCROLL(+(this->h - Panel_headerHeight(this)));
389       break;
390 
391    case KEY_WHEELUP:
392       PANEL_SCROLL(-CRT_scrollWheelVAmount);
393       break;
394 
395    case KEY_WHEELDOWN:
396       PANEL_SCROLL(+CRT_scrollWheelVAmount);
397       break;
398 
399    case KEY_HOME:
400       this->selected = 0;
401       break;
402 
403    case KEY_END:
404       this->selected = size - 1;
405       break;
406 
407    case KEY_CTRL('A'):
408    case '^':
409       this->scrollH = 0;
410       this->needsRedraw = true;
411       break;
412    case KEY_CTRL('E'):
413    case '$':
414       this->scrollH = MAXIMUM(this->selectedLen - this->w, 0);
415       this->needsRedraw = true;
416       break;
417    default:
418       return false;
419    }
420 
421    #undef PANEL_SCROLL
422 
423    // ensure selection within bounds
424    if (this->selected < 0 || size == 0) {
425       this->selected = 0;
426       this->needsRedraw = true;
427    } else if (this->selected >= size) {
428       this->selected = size - 1;
429       this->needsRedraw = true;
430    }
431 
432    return true;
433 }
434 
435 
Panel_selectByTyping(Panel * this,int ch)436 HandlerResult Panel_selectByTyping(Panel* this, int ch) {
437    int size = Panel_size(this);
438 
439    if (!this->eventHandlerState)
440       this->eventHandlerState = xCalloc(100, sizeof(char));
441    char* buffer = this->eventHandlerState;
442 
443    if (0 < ch && ch < 255 && isgraph((unsigned char)ch)) {
444       int len = strlen(buffer);
445       if (!len) {
446          if ('/' == ch) {
447             ch = '\001';
448          } else if ('q' == ch) {
449             return BREAK_LOOP;
450          }
451       } else if (1 == len && '\001' == buffer[0]) {
452          len--;
453       }
454 
455       if (len < 99) {
456          buffer[len] = (char) ch;
457          buffer[len + 1] = '\0';
458       }
459 
460       for (int try = 0; try < 2; try++) {
461          len = strlen(buffer);
462          for (int i = 0; i < size; i++) {
463             const char* cur = ((ListItem*) Panel_get(this, i))->value;
464             while (*cur == ' ') cur++;
465             if (strncasecmp(cur, buffer, len) == 0) {
466                Panel_setSelected(this, i);
467                return HANDLED;
468             }
469          }
470 
471          // if current word did not match,
472          // retry considering the character the start of a new word.
473          buffer[0] = (char) ch;
474          buffer[1] = '\0';
475       }
476 
477       return HANDLED;
478    } else if (ch != ERR) {
479       buffer[0] = '\0';
480    }
481 
482    if (ch == 13) {
483       return BREAK_LOOP;
484    }
485 
486    return IGNORED;
487 }
488