1#include "../util.h"
2#include "../cache.h"
3#include "../../perf.h"
4#include "libslang.h"
5#include "ui.h"
6#include "util.h"
7#include <linux/compiler.h>
8#include <linux/list.h>
9#include <linux/rbtree.h>
10#include <stdlib.h>
11#include <sys/ttydefaults.h>
12#include "browser.h"
13#include "helpline.h"
14#include "keysyms.h"
15#include "../color.h"
16
17static int ui_browser__percent_color(struct ui_browser *browser,
18				     double percent, bool current)
19{
20	if (current && (!browser->use_navkeypressed || browser->navkeypressed))
21		return HE_COLORSET_SELECTED;
22	if (percent >= MIN_RED)
23		return HE_COLORSET_TOP;
24	if (percent >= MIN_GREEN)
25		return HE_COLORSET_MEDIUM;
26	return HE_COLORSET_NORMAL;
27}
28
29int ui_browser__set_color(struct ui_browser *browser, int color)
30{
31	int ret = browser->current_color;
32	browser->current_color = color;
33	SLsmg_set_color(color);
34	return ret;
35}
36
37void ui_browser__set_percent_color(struct ui_browser *browser,
38				   double percent, bool current)
39{
40	 int color = ui_browser__percent_color(browser, percent, current);
41	 ui_browser__set_color(browser, color);
42}
43
44void ui_browser__gotorc(struct ui_browser *browser, int y, int x)
45{
46	SLsmg_gotorc(browser->y + y, browser->x + x);
47}
48
49static struct list_head *
50ui_browser__list_head_filter_entries(struct ui_browser *browser,
51				     struct list_head *pos)
52{
53	do {
54		if (!browser->filter || !browser->filter(browser, pos))
55			return pos;
56		pos = pos->next;
57	} while (pos != browser->entries);
58
59	return NULL;
60}
61
62static struct list_head *
63ui_browser__list_head_filter_prev_entries(struct ui_browser *browser,
64					  struct list_head *pos)
65{
66	do {
67		if (!browser->filter || !browser->filter(browser, pos))
68			return pos;
69		pos = pos->prev;
70	} while (pos != browser->entries);
71
72	return NULL;
73}
74
75void ui_browser__list_head_seek(struct ui_browser *browser, off_t offset, int whence)
76{
77	struct list_head *head = browser->entries;
78	struct list_head *pos;
79
80	if (browser->nr_entries == 0)
81		return;
82
83	switch (whence) {
84	case SEEK_SET:
85		pos = ui_browser__list_head_filter_entries(browser, head->next);
86		break;
87	case SEEK_CUR:
88		pos = browser->top;
89		break;
90	case SEEK_END:
91		pos = ui_browser__list_head_filter_prev_entries(browser, head->prev);
92		break;
93	default:
94		return;
95	}
96
97	assert(pos != NULL);
98
99	if (offset > 0) {
100		while (offset-- != 0)
101			pos = ui_browser__list_head_filter_entries(browser, pos->next);
102	} else {
103		while (offset++ != 0)
104			pos = ui_browser__list_head_filter_prev_entries(browser, pos->prev);
105	}
106
107	browser->top = pos;
108}
109
110void ui_browser__rb_tree_seek(struct ui_browser *browser, off_t offset, int whence)
111{
112	struct rb_root *root = browser->entries;
113	struct rb_node *nd;
114
115	switch (whence) {
116	case SEEK_SET:
117		nd = rb_first(root);
118		break;
119	case SEEK_CUR:
120		nd = browser->top;
121		break;
122	case SEEK_END:
123		nd = rb_last(root);
124		break;
125	default:
126		return;
127	}
128
129	if (offset > 0) {
130		while (offset-- != 0)
131			nd = rb_next(nd);
132	} else {
133		while (offset++ != 0)
134			nd = rb_prev(nd);
135	}
136
137	browser->top = nd;
138}
139
140unsigned int ui_browser__rb_tree_refresh(struct ui_browser *browser)
141{
142	struct rb_node *nd;
143	int row = 0;
144
145	if (browser->top == NULL)
146                browser->top = rb_first(browser->entries);
147
148	nd = browser->top;
149
150	while (nd != NULL) {
151		ui_browser__gotorc(browser, row, 0);
152		browser->write(browser, nd, row);
153		if (++row == browser->rows)
154			break;
155		nd = rb_next(nd);
156	}
157
158	return row;
159}
160
161bool ui_browser__is_current_entry(struct ui_browser *browser, unsigned row)
162{
163	return browser->top_idx + row == browser->index;
164}
165
166void ui_browser__refresh_dimensions(struct ui_browser *browser)
167{
168	browser->width = SLtt_Screen_Cols - 1;
169	browser->height = browser->rows = SLtt_Screen_Rows - 2;
170	browser->y = 1;
171	browser->x = 0;
172}
173
174void ui_browser__handle_resize(struct ui_browser *browser)
175{
176	ui__refresh_dimensions(false);
177	ui_browser__show(browser, browser->title, ui_helpline__current);
178	ui_browser__refresh(browser);
179}
180
181int ui_browser__warning(struct ui_browser *browser, int timeout,
182			const char *format, ...)
183{
184	va_list args;
185	char *text;
186	int key = 0, err;
187
188	va_start(args, format);
189	err = vasprintf(&text, format, args);
190	va_end(args);
191
192	if (err < 0) {
193		va_start(args, format);
194		ui_helpline__vpush(format, args);
195		va_end(args);
196	} else {
197		while ((key = ui__question_window("Warning!", text,
198						   "Press any key...",
199						   timeout)) == K_RESIZE)
200			ui_browser__handle_resize(browser);
201		free(text);
202	}
203
204	return key;
205}
206
207int ui_browser__help_window(struct ui_browser *browser, const char *text)
208{
209	int key;
210
211	while ((key = ui__help_window(text)) == K_RESIZE)
212		ui_browser__handle_resize(browser);
213
214	return key;
215}
216
217bool ui_browser__dialog_yesno(struct ui_browser *browser, const char *text)
218{
219	int key;
220
221	while ((key = ui__dialog_yesno(text)) == K_RESIZE)
222		ui_browser__handle_resize(browser);
223
224	return key == K_ENTER || toupper(key) == 'Y';
225}
226
227void ui_browser__reset_index(struct ui_browser *browser)
228{
229	browser->index = browser->top_idx = 0;
230	browser->seek(browser, 0, SEEK_SET);
231}
232
233void __ui_browser__show_title(struct ui_browser *browser, const char *title)
234{
235	SLsmg_gotorc(0, 0);
236	ui_browser__set_color(browser, HE_COLORSET_ROOT);
237	slsmg_write_nstring(title, browser->width + 1);
238}
239
240void ui_browser__show_title(struct ui_browser *browser, const char *title)
241{
242	pthread_mutex_lock(&ui__lock);
243	__ui_browser__show_title(browser, title);
244	pthread_mutex_unlock(&ui__lock);
245}
246
247int ui_browser__show(struct ui_browser *browser, const char *title,
248		     const char *helpline, ...)
249{
250	int err;
251	va_list ap;
252
253	if (browser->refresh_dimensions == NULL)
254		browser->refresh_dimensions = ui_browser__refresh_dimensions;
255
256	browser->refresh_dimensions(browser);
257
258	pthread_mutex_lock(&ui__lock);
259	__ui_browser__show_title(browser, title);
260
261	browser->title = title;
262	zfree(&browser->helpline);
263
264	va_start(ap, helpline);
265	err = vasprintf(&browser->helpline, helpline, ap);
266	va_end(ap);
267	if (err > 0)
268		ui_helpline__push(browser->helpline);
269	pthread_mutex_unlock(&ui__lock);
270	return err ? 0 : -1;
271}
272
273void ui_browser__hide(struct ui_browser *browser)
274{
275	pthread_mutex_lock(&ui__lock);
276	ui_helpline__pop();
277	zfree(&browser->helpline);
278	pthread_mutex_unlock(&ui__lock);
279}
280
281static void ui_browser__scrollbar_set(struct ui_browser *browser)
282{
283	int height = browser->height, h = 0, pct = 0,
284	    col = browser->width,
285	    row = 0;
286
287	if (browser->nr_entries > 1) {
288		pct = ((browser->index * (browser->height - 1)) /
289		       (browser->nr_entries - 1));
290	}
291
292	SLsmg_set_char_set(1);
293
294	while (h < height) {
295	        ui_browser__gotorc(browser, row++, col);
296		SLsmg_write_char(h == pct ? SLSMG_DIAMOND_CHAR : SLSMG_CKBRD_CHAR);
297		++h;
298	}
299
300	SLsmg_set_char_set(0);
301}
302
303static int __ui_browser__refresh(struct ui_browser *browser)
304{
305	int row;
306	int width = browser->width;
307
308	row = browser->refresh(browser);
309	ui_browser__set_color(browser, HE_COLORSET_NORMAL);
310
311	if (!browser->use_navkeypressed || browser->navkeypressed)
312		ui_browser__scrollbar_set(browser);
313	else
314		width += 1;
315
316	SLsmg_fill_region(browser->y + row, browser->x,
317			  browser->height - row, width, ' ');
318
319	return 0;
320}
321
322int ui_browser__refresh(struct ui_browser *browser)
323{
324	pthread_mutex_lock(&ui__lock);
325	__ui_browser__refresh(browser);
326	pthread_mutex_unlock(&ui__lock);
327
328	return 0;
329}
330
331/*
332 * Here we're updating nr_entries _after_ we started browsing, i.e.  we have to
333 * forget about any reference to any entry in the underlying data structure,
334 * that is why we do a SEEK_SET. Think about 'perf top' in the hists browser
335 * after an output_resort and hist decay.
336 */
337void ui_browser__update_nr_entries(struct ui_browser *browser, u32 nr_entries)
338{
339	off_t offset = nr_entries - browser->nr_entries;
340
341	browser->nr_entries = nr_entries;
342
343	if (offset < 0) {
344		if (browser->top_idx < (u64)-offset)
345			offset = -browser->top_idx;
346
347		browser->index += offset;
348		browser->top_idx += offset;
349	}
350
351	browser->top = NULL;
352	browser->seek(browser, browser->top_idx, SEEK_SET);
353}
354
355int ui_browser__run(struct ui_browser *browser, int delay_secs)
356{
357	int err, key;
358
359	while (1) {
360		off_t offset;
361
362		pthread_mutex_lock(&ui__lock);
363		err = __ui_browser__refresh(browser);
364		SLsmg_refresh();
365		pthread_mutex_unlock(&ui__lock);
366		if (err < 0)
367			break;
368
369		key = ui__getch(delay_secs);
370
371		if (key == K_RESIZE) {
372			ui__refresh_dimensions(false);
373			browser->refresh_dimensions(browser);
374			__ui_browser__show_title(browser, browser->title);
375			ui_helpline__puts(browser->helpline);
376			continue;
377		}
378
379		if (browser->use_navkeypressed && !browser->navkeypressed) {
380			if (key == K_DOWN || key == K_UP ||
381			    key == K_PGDN || key == K_PGUP ||
382			    key == K_HOME || key == K_END ||
383			    key == ' ') {
384				browser->navkeypressed = true;
385				continue;
386			} else
387				return key;
388		}
389
390		switch (key) {
391		case K_DOWN:
392			if (browser->index == browser->nr_entries - 1)
393				break;
394			++browser->index;
395			if (browser->index == browser->top_idx + browser->rows) {
396				++browser->top_idx;
397				browser->seek(browser, +1, SEEK_CUR);
398			}
399			break;
400		case K_UP:
401			if (browser->index == 0)
402				break;
403			--browser->index;
404			if (browser->index < browser->top_idx) {
405				--browser->top_idx;
406				browser->seek(browser, -1, SEEK_CUR);
407			}
408			break;
409		case K_PGDN:
410		case ' ':
411			if (browser->top_idx + browser->rows > browser->nr_entries - 1)
412				break;
413
414			offset = browser->rows;
415			if (browser->index + offset > browser->nr_entries - 1)
416				offset = browser->nr_entries - 1 - browser->index;
417			browser->index += offset;
418			browser->top_idx += offset;
419			browser->seek(browser, +offset, SEEK_CUR);
420			break;
421		case K_PGUP:
422			if (browser->top_idx == 0)
423				break;
424
425			if (browser->top_idx < browser->rows)
426				offset = browser->top_idx;
427			else
428				offset = browser->rows;
429
430			browser->index -= offset;
431			browser->top_idx -= offset;
432			browser->seek(browser, -offset, SEEK_CUR);
433			break;
434		case K_HOME:
435			ui_browser__reset_index(browser);
436			break;
437		case K_END:
438			offset = browser->rows - 1;
439			if (offset >= browser->nr_entries)
440				offset = browser->nr_entries - 1;
441
442			browser->index = browser->nr_entries - 1;
443			browser->top_idx = browser->index - offset;
444			browser->seek(browser, -offset, SEEK_END);
445			break;
446		default:
447			return key;
448		}
449	}
450	return -1;
451}
452
453unsigned int ui_browser__list_head_refresh(struct ui_browser *browser)
454{
455	struct list_head *pos;
456	struct list_head *head = browser->entries;
457	int row = 0;
458
459	if (browser->top == NULL || browser->top == browser->entries)
460                browser->top = ui_browser__list_head_filter_entries(browser, head->next);
461
462	pos = browser->top;
463
464	list_for_each_from(pos, head) {
465		if (!browser->filter || !browser->filter(browser, pos)) {
466			ui_browser__gotorc(browser, row, 0);
467			browser->write(browser, pos, row);
468			if (++row == browser->rows)
469				break;
470		}
471	}
472
473	return row;
474}
475
476static struct ui_browser_colorset {
477	const char *name, *fg, *bg;
478	int colorset;
479} ui_browser__colorsets[] = {
480	{
481		.colorset = HE_COLORSET_TOP,
482		.name	  = "top",
483		.fg	  = "red",
484		.bg	  = "default",
485	},
486	{
487		.colorset = HE_COLORSET_MEDIUM,
488		.name	  = "medium",
489		.fg	  = "green",
490		.bg	  = "default",
491	},
492	{
493		.colorset = HE_COLORSET_NORMAL,
494		.name	  = "normal",
495		.fg	  = "default",
496		.bg	  = "default",
497	},
498	{
499		.colorset = HE_COLORSET_SELECTED,
500		.name	  = "selected",
501		.fg	  = "black",
502		.bg	  = "lightgray",
503	},
504	{
505		.colorset = HE_COLORSET_CODE,
506		.name	  = "code",
507		.fg	  = "blue",
508		.bg	  = "default",
509	},
510	{
511		.colorset = HE_COLORSET_ADDR,
512		.name	  = "addr",
513		.fg	  = "magenta",
514		.bg	  = "default",
515	},
516	{
517		.colorset = HE_COLORSET_ROOT,
518		.name	  = "root",
519		.fg	  = "white",
520		.bg	  = "blue",
521	},
522	{
523		.name = NULL,
524	}
525};
526
527
528static int ui_browser__color_config(const char *var, const char *value,
529				    void *data __maybe_unused)
530{
531	char *fg = NULL, *bg;
532	int i;
533
534	/* same dir for all commands */
535	if (prefixcmp(var, "colors.") != 0)
536		return 0;
537
538	for (i = 0; ui_browser__colorsets[i].name != NULL; ++i) {
539		const char *name = var + 7;
540
541		if (strcmp(ui_browser__colorsets[i].name, name) != 0)
542			continue;
543
544		fg = strdup(value);
545		if (fg == NULL)
546			break;
547
548		bg = strchr(fg, ',');
549		if (bg == NULL)
550			break;
551
552		*bg = '\0';
553		while (isspace(*++bg));
554		ui_browser__colorsets[i].bg = bg;
555		ui_browser__colorsets[i].fg = fg;
556		return 0;
557	}
558
559	free(fg);
560	return -1;
561}
562
563void ui_browser__argv_seek(struct ui_browser *browser, off_t offset, int whence)
564{
565	switch (whence) {
566	case SEEK_SET:
567		browser->top = browser->entries;
568		break;
569	case SEEK_CUR:
570		browser->top = browser->top + browser->top_idx + offset;
571		break;
572	case SEEK_END:
573		browser->top = browser->top + browser->nr_entries - 1 + offset;
574		break;
575	default:
576		return;
577	}
578}
579
580unsigned int ui_browser__argv_refresh(struct ui_browser *browser)
581{
582	unsigned int row = 0, idx = browser->top_idx;
583	char **pos;
584
585	if (browser->top == NULL)
586		browser->top = browser->entries;
587
588	pos = (char **)browser->top;
589	while (idx < browser->nr_entries) {
590		if (!browser->filter || !browser->filter(browser, *pos)) {
591			ui_browser__gotorc(browser, row, 0);
592			browser->write(browser, pos, row);
593			if (++row == browser->rows)
594				break;
595		}
596
597		++idx;
598		++pos;
599	}
600
601	return row;
602}
603
604void __ui_browser__vline(struct ui_browser *browser, unsigned int column,
605			 u16 start, u16 end)
606{
607	SLsmg_set_char_set(1);
608	ui_browser__gotorc(browser, start, column);
609	SLsmg_draw_vline(end - start + 1);
610	SLsmg_set_char_set(0);
611}
612
613void ui_browser__write_graph(struct ui_browser *browser __maybe_unused,
614			     int graph)
615{
616	SLsmg_set_char_set(1);
617	SLsmg_write_char(graph);
618	SLsmg_set_char_set(0);
619}
620
621static void __ui_browser__line_arrow_up(struct ui_browser *browser,
622					unsigned int column,
623					u64 start, u64 end)
624{
625	unsigned int row, end_row;
626
627	SLsmg_set_char_set(1);
628
629	if (start < browser->top_idx + browser->rows) {
630		row = start - browser->top_idx;
631		ui_browser__gotorc(browser, row, column);
632		SLsmg_write_char(SLSMG_LLCORN_CHAR);
633		ui_browser__gotorc(browser, row, column + 1);
634		SLsmg_draw_hline(2);
635
636		if (row-- == 0)
637			goto out;
638	} else
639		row = browser->rows - 1;
640
641	if (end > browser->top_idx)
642		end_row = end - browser->top_idx;
643	else
644		end_row = 0;
645
646	ui_browser__gotorc(browser, end_row, column);
647	SLsmg_draw_vline(row - end_row + 1);
648
649	ui_browser__gotorc(browser, end_row, column);
650	if (end >= browser->top_idx) {
651		SLsmg_write_char(SLSMG_ULCORN_CHAR);
652		ui_browser__gotorc(browser, end_row, column + 1);
653		SLsmg_write_char(SLSMG_HLINE_CHAR);
654		ui_browser__gotorc(browser, end_row, column + 2);
655		SLsmg_write_char(SLSMG_RARROW_CHAR);
656	}
657out:
658	SLsmg_set_char_set(0);
659}
660
661static void __ui_browser__line_arrow_down(struct ui_browser *browser,
662					  unsigned int column,
663					  u64 start, u64 end)
664{
665	unsigned int row, end_row;
666
667	SLsmg_set_char_set(1);
668
669	if (start >= browser->top_idx) {
670		row = start - browser->top_idx;
671		ui_browser__gotorc(browser, row, column);
672		SLsmg_write_char(SLSMG_ULCORN_CHAR);
673		ui_browser__gotorc(browser, row, column + 1);
674		SLsmg_draw_hline(2);
675
676		if (row++ == 0)
677			goto out;
678	} else
679		row = 0;
680
681	if (end >= browser->top_idx + browser->rows)
682		end_row = browser->rows - 1;
683	else
684		end_row = end - browser->top_idx;
685
686	ui_browser__gotorc(browser, row, column);
687	SLsmg_draw_vline(end_row - row + 1);
688
689	ui_browser__gotorc(browser, end_row, column);
690	if (end < browser->top_idx + browser->rows) {
691		SLsmg_write_char(SLSMG_LLCORN_CHAR);
692		ui_browser__gotorc(browser, end_row, column + 1);
693		SLsmg_write_char(SLSMG_HLINE_CHAR);
694		ui_browser__gotorc(browser, end_row, column + 2);
695		SLsmg_write_char(SLSMG_RARROW_CHAR);
696	}
697out:
698	SLsmg_set_char_set(0);
699}
700
701void __ui_browser__line_arrow(struct ui_browser *browser, unsigned int column,
702			      u64 start, u64 end)
703{
704	if (start > end)
705		__ui_browser__line_arrow_up(browser, column, start, end);
706	else
707		__ui_browser__line_arrow_down(browser, column, start, end);
708}
709
710void ui_browser__init(void)
711{
712	int i = 0;
713
714	perf_config(ui_browser__color_config, NULL);
715
716	while (ui_browser__colorsets[i].name) {
717		struct ui_browser_colorset *c = &ui_browser__colorsets[i++];
718		sltt_set_color(c->colorset, c->name, c->fg, c->bg);
719	}
720
721	annotate_browser__init();
722}
723