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