1 /*
2  * widgets.c
3  *
4  * Copyright (C) Johan Malm 2017-2019
5  *
6  * A very simple widget implementation
7  *
8  * We read lines beginning with '@' from jgmenu flavoured CSV file and parses in
9  * accordance with the following syntax:
10  *
11  * @type,action,x,y,w,h,r,halign,valign,fgcol,bgcol,content
12  *
13  * where
14  *	- action = what to do when clicked
15  *	- (x, y) = margin
16  *	- (w, h) = size
17  *	- r = corner radius
18  *	- content = icon_path or text
19  * note
20  *	- For RECT, a 1px thick border will be drawn using fgcol
21  */
22 
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 
27 #include "util.h"
28 #include "list.h"
29 #include "argv-buf.h"
30 #include "align.h"
31 #include "x11-ui.h"
32 #include "config.h"
33 #include "geometry.h"
34 #include "sbuf.h"
35 #include "filter.h"
36 #include "icon.h"
37 #include "x11-ui.h"
38 #include "widgets.h"
39 #include "banned.h"
40 
41 enum widget_type { WIDGET_ERROR, ICON, RECT, TEXT, SEARCH };
42 
43 static LIST_HEAD(widgets);
44 
45 /* indicates if mouse is over any of the widgets */
46 static int mouseover;
47 
48 /* "widgets" has control of keyboard movement keys */
49 static int keyboard_grabbed;
50 
51 struct widget {
52 	char *buf;
53 	enum widget_type type;
54 	char *action;
55 	int x;
56 	int y;
57 	int w;
58 	int h;
59 	int r;
60 	enum alignment halign;
61 	enum alignment valign;
62 	double fgcol[4];
63 	double bgcol[4];
64 	char *content;
65 	cairo_surface_t *surface;
66 	struct list_head list;
67 };
68 
69 static struct widget *selection;
70 
parse_type(const char * field)71 static enum widget_type parse_type(const char *field)
72 {
73 	if (!field || !field[0])
74 		return WIDGET_ERROR;
75 	if (!strcmp(field, "icon"))
76 		return ICON;
77 	if (!strcmp(field, "rect"))
78 		return RECT;
79 	if (!strcmp(field, "search"))
80 		return SEARCH;
81 	if (!strcmp(field, "text"))
82 		return TEXT;
83 	return WIDGET_ERROR;
84 }
85 
draw_icon(struct widget ** w)86 static void draw_icon(struct widget **w)
87 {
88 	if (!(*w)->surface)
89 		(*w)->surface = load_cairo_icon((*w)->content, (*w)->w);
90 	if (!(*w)->surface)
91 		return;
92 	ui_insert_image((*w)->surface, (*w)->x, (*w)->y, (*w)->w, 1.0);
93 }
94 
draw_rect(struct widget ** w)95 static void draw_rect(struct widget **w)
96 {
97 	ui_draw_rectangle((*w)->x, (*w)->y, (*w)->w, (*w)->h, (*w)->r,
98 			  0.0, 1, (*w)->bgcol);
99 	ui_draw_rectangle((*w)->x, (*w)->y, (*w)->w, (*w)->h, (*w)->r,
100 			  1.0, 0, (*w)->fgcol);
101 }
102 
draw_search(struct widget ** w)103 static void draw_search(struct widget **w)
104 {
105 	char *t;
106 	int padding_left = 4;
107 
108 	if (filter_needle_length())
109 		t = filter_strdup_needle();
110 	else
111 		t = xstrdup((*w)->content);
112 	ui_insert_text(t, (*w)->x + padding_left, (*w)->y, (*w)->h, (*w)->w,
113 		       (*w)->fgcol, LEFT);
114 	xfree(t);
115 }
116 
draw_text(struct widget ** w)117 static void draw_text(struct widget **w)
118 {
119 	ui_insert_text((*w)->content, (*w)->x, (*w)->y, (*w)->h, (*w)->w,
120 		       (*w)->fgcol, LEFT);
121 }
122 
draw_selection(struct widget ** w)123 static void draw_selection(struct widget **w)
124 {
125 	if (!(*w)->action || (*w)->action[0] == '\0')
126 		return;
127 	ui_draw_rectangle((*w)->x, (*w)->y, (*w)->w, (*w)->h, (*w)->r,
128 			  0.0, 1, config.color_sel_bg);
129 	ui_draw_rectangle((*w)->x, (*w)->y, (*w)->w, (*w)->h, (*w)->r,
130 			  1.0, 0, config.color_sel_border);
131 }
132 
widgets_select(const char * ksym)133 void widgets_select(const char *ksym)
134 {
135 	struct widget *w;
136 	enum direction {UNKNOWN, NEXT, PREV};
137 	enum direction direction = UNKNOWN;
138 
139 	/*
140 	 * 'selection' is set when widgets are initiates
141 	 * if selection == 0, there are no selectable widgets
142 	 */
143 	if (!selection)
144 		return;
145 	w = selection;
146 	if (!strcmp(ksym, "XK_Down"))
147 		direction = NEXT;
148 	else if (!strcmp(ksym, "XK_Up"))
149 		direction = PREV;
150 	if (direction == UNKNOWN)
151 		warn("unknown string passed to %s", __func__);
152 	for (;;) {
153 		switch (direction) {
154 		case NEXT:
155 			w = container_of(w->list.next, struct widget, list);
156 			break;
157 		case PREV:
158 			w = container_of(w->list.prev, struct widget, list);
159 			break;
160 		case UNKNOWN:
161 			return;
162 		}
163 		if (!w->action || w->action[0] == '\0')
164 			continue;
165 		selection = w;
166 		break;
167 	}
168 }
169 
widgets_get_kb_grabbed(void)170 int widgets_get_kb_grabbed(void)
171 {
172 	return keyboard_grabbed;
173 }
174 
widgets_toggle_kb_grabbed(void)175 void widgets_toggle_kb_grabbed(void)
176 {
177 	keyboard_grabbed = keyboard_grabbed ? 0 : 1;
178 }
179 
widgets_mouseover(void)180 int widgets_mouseover(void)
181 {
182 	return mouseover;
183 }
184 
widgets_set_pointer_position(int x,int y)185 void widgets_set_pointer_position(int x, int y)
186 {
187 	struct widget *w;
188 	struct area widget_area;
189 	struct point pointer;
190 
191 	mouseover = 0;
192 	pointer.x = x;
193 	pointer.y = y;
194 	list_for_each_entry(w, &widgets, list) {
195 		widget_area.x = w->x;
196 		widget_area.y = w->y;
197 		widget_area.w = w->w;
198 		widget_area.h = w->h;
199 		if (ui_is_point_in_area(pointer, widget_area)) {
200 			selection = w;
201 			mouseover = 1;
202 			break;
203 		}
204 	}
205 }
206 
207 /*
208  * widgets_set_point_position() should be run just before calling
209  * this function
210  */
widgets_get_selection_action(void)211 char *widgets_get_selection_action(void)
212 {
213 	struct widget *w;
214 
215 	list_for_each_entry(w, &widgets, list) {
216 		if (selection == w) {
217 			if (!w->action || w->action[0] == '\0')
218 				continue;
219 			return w->action;
220 		}
221 	}
222 	return NULL;
223 }
224 
widgets_draw(void)225 void widgets_draw(void)
226 {
227 	struct widget *w;
228 
229 	if (list_empty(&widgets))
230 		return;
231 	list_for_each_entry(w, &widgets, list) {
232 		if (selection == w)
233 			draw_selection(&w);
234 		if (w->type == ICON)
235 			draw_icon(&w);
236 		else if (w->type == RECT)
237 			draw_rect(&w);
238 		else if (w->type == SEARCH)
239 			draw_search(&w);
240 		else if (w->type == TEXT)
241 			draw_text(&w);
242 		else
243 			warn("widget type not recognised");
244 	}
245 }
246 
color_copy(double to[4],double from[4])247 static void color_copy(double to[4], double from[4])
248 {
249 	int i;
250 
251 	for (i = 0; i < 4; i++)
252 		to[i] = from[i];
253 }
254 
widgets_add(const char * s)255 void widgets_add(const char *s)
256 {
257 	struct argv_buf argv_buf;
258 	struct widget *w;
259 
260 	w = xmalloc(sizeof(struct widget));
261 	argv_init(&argv_buf);
262 	argv_set_delim(&argv_buf, ',');
263 	argv_strdup(&argv_buf, s);
264 	argv_parse(&argv_buf);
265 	if (argv_buf.argc != 12)
266 		warn("widget did not contain 12 fields");
267 	w->buf = argv_buf.buf;
268 	w->type = parse_type(argv_buf.argv[0] + 1);
269 	w->action = argv_buf.argv[1];
270 	remove_caret_markup_closing_bracket(w->action);
271 	xatoi(&w->x, argv_buf.argv[2], XATOI_NONNEG, "w->x");
272 	xatoi(&w->y, argv_buf.argv[3], XATOI_NONNEG, "w->y");
273 	xatoi(&w->w, argv_buf.argv[4], XATOI_NONNEG, "w->w");
274 	xatoi(&w->h, argv_buf.argv[5], XATOI_NONNEG, "w->h");
275 	xatoi(&w->r, argv_buf.argv[6], XATOI_NONNEG, "w->r");
276 /*	enum alignment halign; */
277 /*	enum alignment valign; */
278 	if (!strcasecmp(argv_buf.argv[9], "auto"))
279 		color_copy(w->fgcol, config.color_norm_fg);
280 	else
281 		parse_hexstr(argv_buf.argv[9], w->fgcol);
282 	parse_hexstr(argv_buf.argv[10], w->bgcol);
283 	w->content = argv_buf.argv[11];
284 	w->surface = NULL;
285 	list_add_tail(&w->list, &widgets);
286 	if (selection)
287 		return;
288 	if (w->action && w->action[0] != '\0')
289 		selection = w;
290 }
291 
widgets_cleanup(void)292 void widgets_cleanup(void)
293 {
294 	struct widget *w, *tmp_w;
295 
296 	list_for_each_entry_safe(w, tmp_w, &widgets, list) {
297 		xfree(w->buf);
298 		list_del(&w->list);
299 		xfree(w);
300 	}
301 }
302