1 /*
2  * Copyright (c) 2015-2021 Hanspeter Portner (dev@open-music-kontrollers.ch)
3  *
4  * This is free software: you can redistribute it and/or modify
5  * it under the terms of the Artistic License 2.0 as published by
6  * The Perl Foundation.
7  *
8  * This source is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  * Artistic License 2.0 for more details.
12  *
13  * You should have received a copy of the Artistic License 2.0
14  * along the source as a COPYING file. If not, obtain it from
15  * http://www.perlfoundation.org/artistic_license_2_0.
16  */
17 
18 #include <ctype.h>
19 #include <inttypes.h>
20 
21 #include <sherlock.h>
22 #include <sherlock_nk.h>
23 #include <encoder.h>
24 
25 #ifdef Bool // hack for xlib
26 #	undef Bool
27 #endif
28 
29 #define NS_RDF (const uint8_t*)"http://www.w3.org/1999/02/22-rdf-syntax-ns#"
30 #define NS_RDFS (const uint8_t*)"http://www.w3.org/2000/01/rdf-schema#"
31 #define NS_XSD (const uint8_t*)"http://www.w3.org/2001/XMLSchema#"
32 #define NS_OSC (const uint8_t*)"http://open-music-kontrollers.ch/lv2/osc#"
33 #define NS_XPRESS (const uint8_t*)"http://open-music-kontrollers.ch/lv2/xpress#"
34 #define NS_SPOD (const uint8_t*)"http://open-music-kontrollers.ch/lv2/synthpod#"
35 #define NS_CANVAS (const uint8_t*)"http://open-music-kontrollers.ch/lv2/canvas#"
36 
37 // copyied and adapted from libsratom
38 static inline char *
_sratom_to_turtle(Sratom * sratom,LV2_URID_Unmap * unmap,const char * base_uri,const SerdNode * subject,const SerdNode * predicate,uint32_t type,uint32_t size,const void * body)39 _sratom_to_turtle(Sratom*         sratom,
40                  LV2_URID_Unmap* unmap,
41                  const char*     base_uri,
42                  const SerdNode* subject,
43                  const SerdNode* predicate,
44                  uint32_t        type,
45                  uint32_t        size,
46                  const void*     body)
47 {
48 	SerdURI   buri = SERD_URI_NULL;
49 	SerdNode  base = serd_node_new_uri_from_string((uint8_t *)(base_uri), NULL, &buri);
50 	SerdEnv*  env  = serd_env_new(&base);
51 	SerdChunk str  = { NULL, 0 };
52 
53 	serd_env_set_prefix_from_strings(env, (const uint8_t *)"rdf", NS_RDF);
54 	serd_env_set_prefix_from_strings(env, (const uint8_t *)"rdfs", NS_RDFS);
55 	serd_env_set_prefix_from_strings(env, (const uint8_t *)"xsd", NS_XSD);
56 	serd_env_set_prefix_from_strings(env, (const uint8_t *)"lv2", (const uint8_t *)LV2_CORE_PREFIX);
57 	serd_env_set_prefix_from_strings(env, (const uint8_t *)"midi", (const uint8_t *)LV2_MIDI_PREFIX);
58 	serd_env_set_prefix_from_strings(env, (const uint8_t *)"atom", (const uint8_t *)LV2_ATOM_PREFIX);
59 	serd_env_set_prefix_from_strings(env, (const uint8_t *)"units", (const uint8_t *)LV2_UNITS_PREFIX);
60 	serd_env_set_prefix_from_strings(env, (const uint8_t *)"ui", (const uint8_t *)LV2_UI_PREFIX);
61 	serd_env_set_prefix_from_strings(env, (const uint8_t *)"time", (const uint8_t *)LV2_TIME_URI"#");
62 	serd_env_set_prefix_from_strings(env, (const uint8_t *)"patch", (const uint8_t *)LV2_PATCH_PREFIX);
63 	serd_env_set_prefix_from_strings(env, (const uint8_t *)"state", (const uint8_t *)LV2_STATE_PREFIX);
64 
65 	serd_env_set_prefix_from_strings(env, (const uint8_t *)"osc", NS_OSC);
66 	serd_env_set_prefix_from_strings(env, (const uint8_t *)"xpress", NS_XPRESS);
67 	serd_env_set_prefix_from_strings(env, (const uint8_t *)"spod", NS_SPOD);
68 	serd_env_set_prefix_from_strings(env, (const uint8_t *)"canvas", NS_CANVAS);
69 
70 	SerdWriter* writer = serd_writer_new(
71 		SERD_TURTLE,
72 		(SerdStyle)(SERD_STYLE_ABBREVIATED |
73 		            SERD_STYLE_RESOLVED |
74 		            SERD_STYLE_CURIED |
75 								SERD_STYLE_ASCII),
76 		env, &buri, serd_chunk_sink, &str);
77 
78 	// Write @prefix directives
79 	serd_env_foreach(env,
80 	                 (SerdPrefixSink)serd_writer_set_prefix,
81 	                 writer);
82 
83 	sratom_set_sink(sratom, base_uri,
84 	                (SerdStatementSink)serd_writer_write_statement,
85 	                (SerdEndSink)serd_writer_end_anon,
86 	                writer);
87 	sratom_write(sratom, unmap, SERD_EMPTY_S,
88 	             subject, predicate, type, size, body);
89 	serd_writer_finish(writer);
90 
91 	serd_writer_free(writer);
92 	serd_env_free(env);
93 	serd_node_free(&base);
94 	return (char*)serd_chunk_sink_finish(&str);
95 }
96 
97 static void
_set_string(struct nk_str * str,uint32_t size,const char * body)98 _set_string(struct nk_str *str, uint32_t size, const char *body)
99 {
100 	nk_str_clear(str);
101 
102 	// replace tab with 2 spaces
103 	const char *end = body + size - 1;
104 	const char *from = body;
105 	for(const char *to = strchr(from, '\t');
106 		to && (to < end);
107 		from = to + 1, to = strchr(from, '\t'))
108 	{
109 		nk_str_append_text_utf8(str, from, to-from);
110 		nk_str_append_text_utf8(str, "  ", 2);
111 	}
112 	nk_str_append_text_utf8(str, from, end-from);
113 }
114 
115 static inline void
_shadow(struct nk_context * ctx,bool * shadow)116 _shadow(struct nk_context *ctx, bool *shadow)
117 {
118 	if(*shadow)
119 	{
120 		struct nk_style *style = &ctx->style;
121 		const struct nk_vec2 group_padding = style->window.group_padding;
122 		struct nk_command_buffer *canvas = nk_window_get_canvas(ctx);
123 
124 		struct nk_rect b = nk_widget_bounds(ctx);
125 		b.x -= group_padding.x;
126 		b.w *= 10;
127 		b.w += 5*group_padding.x;
128 		nk_fill_rect(canvas, b, 0.f, nk_rgb(0x28, 0x28, 0x28));
129 	}
130 
131 	*shadow = !*shadow;
132 }
133 
134 void
_atom_inspector_expose(struct nk_context * ctx,struct nk_rect wbounds,void * data)135 _atom_inspector_expose(struct nk_context *ctx, struct nk_rect wbounds, void *data)
136 {
137 	plughandle_t *handle = data;
138 
139 	handle->dy = 20.f * _get_scale(handle);
140 	const float widget_h = handle->dy;
141 	struct nk_style *style = &ctx->style;
142   const struct nk_vec2 window_padding = style->window.padding;
143   const struct nk_vec2 group_padding = style->window.group_padding;
144 
145 	style->selectable.normal.data.color.a = 0x0;
146 	style->selectable.hover.data.color.a = 0x0;
147 
148 	const char *window_name = "Sherlock";
149 	if(nk_begin(ctx, window_name, wbounds, NK_WINDOW_NO_SCROLLBAR))
150 	{
151 		struct nk_panel *panel= nk_window_get_panel(ctx);
152 		struct nk_command_buffer *canvas = nk_window_get_canvas(ctx);
153 
154 		const float body_h = panel->bounds.h - 2*window_padding.y;
155 		nk_layout_row_dynamic(ctx, body_h, 2);
156 		if(nk_group_begin(ctx, "Left", NK_WINDOW_NO_SCROLLBAR))
157 		{
158 			{
159 				// has filter URID been updated meanwhile ?
160 				if(handle->filter != handle->state.filter)
161 				{
162 					const char *uri = handle->unmap->unmap(handle->unmap->handle, handle->state.filter);
163 
164 					if(uri)
165 					{
166 						strncpy(handle->filter_uri, uri, sizeof(handle->filter_uri) - 1);
167 					}
168 					else
169 					{
170 						handle->filter_uri[0] = '\0';
171 					}
172 
173 					handle->filter = handle->state.filter;
174 				}
175 
176 				bool dirty = false;
177 
178 				const float n = 4;
179 				const float r0 = 1.f / n;
180 				const float r1 = 0.1f / 3;
181 				const float r2 = r0 - r1;
182 				const float footer [5] = {r1+r2, r1+r2, r1+r2, r1, r2};
183 				nk_layout_row(ctx, NK_DYNAMIC, widget_h, 5, footer);
184 				{
185 					nk_label(ctx, "match:", NK_TEXT_LEFT);
186 
187 					if(nk_button_label(ctx, "time"))
188 					{
189 						strncpy(handle->filter_uri, LV2_TIME__Position, sizeof(handle->filter_uri) - 1);
190 						dirty = true;
191 					}
192 
193 					if(nk_button_label(ctx, "all"))
194 					{
195 						strncpy(handle->filter_uri, SHERLOCK_URI"#matchAll", sizeof(handle->filter_uri) - 1);
196 						dirty = true;
197 					}
198 
199 					const int32_t state_negate = _check(ctx, handle->state.negate);
200 					if(state_negate != handle->state.negate)
201 					{
202 						handle->state.negate = state_negate;
203 						_set_bool(handle, handle->urid.negate, handle->state.negate);
204 					}
205 					nk_label(ctx, "negate", NK_TEXT_LEFT);
206 				}
207 
208 				nk_layout_row_dynamic(ctx, widget_h, 1);
209 				{
210 					const nk_flags flags = NK_EDIT_FIELD
211 						| NK_EDIT_AUTO_SELECT
212 						| NK_EDIT_SIG_ENTER;
213 					//nk_edit_focus(ctx, flags);
214 					nk_flags mode = nk_edit_string_zero_terminated(ctx, flags, handle->filter_uri, sizeof(handle->filter_uri) - 1, nk_filter_ascii);
215 					if(mode & NK_EDIT_COMMITED)
216 					{
217 						if(strlen(handle->filter_uri) == 0)
218 						{
219 							strncpy(handle->filter_uri, SHERLOCK_URI"#matchAll", sizeof(handle->filter_uri) - 1);
220 						}
221 						dirty = true;
222 					}
223 				}
224 
225 				if(dirty)
226 				{
227 					handle->state.filter = handle->map->map(handle->map->handle, handle->filter_uri);
228 					_set_urid(handle, handle->urid.filter, handle->state.filter);
229 				}
230 			}
231 
232 			const float content_h = nk_window_get_height(ctx) - 3*window_padding.y - 7*group_padding.y - 4*widget_h;
233 			nk_layout_row_dynamic(ctx, content_h, 1);
234 			nk_flags flags = NK_WINDOW_BORDER;
235 			if(handle->state.follow)
236 			{
237 				flags |= NK_WINDOW_NO_SCROLLBAR;
238 			}
239 			else
240 			{
241 				handle->shadow = false;
242 			}
243 			struct nk_list_view lview;
244 			if(nk_list_view_begin(ctx, &lview, "Events", flags, widget_h, NK_MIN(handle->n_item, MAX_LINES)))
245 			{
246 				if(handle->state.follow)
247 				{
248 					lview.end = NK_MAX(handle->n_item, 0);
249 					lview.begin = NK_MAX(lview.end - lview.count, 0);
250 				}
251 				for(int l = lview.begin; (l < lview.end) && (l < handle->n_item); l++)
252 				{
253 					item_t *itm = handle->items[l];
254 
255 					switch(itm->type)
256 					{
257 						case ITEM_TYPE_NONE:
258 						{
259 							// skip, never reached
260 						} break;
261 						case ITEM_TYPE_FRAME:
262 						{
263 							nk_layout_row_dynamic(ctx, widget_h, 3);
264 							{
265 								struct nk_rect b = nk_widget_bounds(ctx);
266 								b.x -= group_padding.x;
267 								b.w *= 3;
268 								b.w += 4*group_padding.x;
269 								nk_fill_rect(canvas, b, 0.f, nk_rgb(0x18, 0x18, 0x18));
270 							}
271 
272 							nk_labelf_colored(ctx, NK_TEXT_LEFT, orange, "@%"PRIi64, itm->frame.offset);
273 							nk_labelf_colored(ctx, NK_TEXT_CENTERED, green, "-%"PRIu32"-", itm->frame.counter);
274 							nk_labelf_colored(ctx, NK_TEXT_RIGHT, violet, "%"PRIi32, itm->frame.nsamples);
275 
276 							handle->shadow = false;
277 						} break;
278 
279 						case ITEM_TYPE_EVENT:
280 						{
281 							LV2_Atom_Event *ev = &itm->event.ev;
282 							const LV2_Atom *body = &ev->body;
283 							const int64_t frames = ev->time.frames;
284 							const char *uri = NULL;
285 							if(lv2_atom_forge_is_object_type(&handle->forge, body->type))
286 							{
287 								const LV2_Atom_Object *obj = (const LV2_Atom_Object *)body;
288 
289 								if(obj->body.otype)
290 									uri = handle->unmap->unmap(handle->unmap->handle, obj->body.otype);
291 								else if(obj->body.id)
292 									uri = handle->unmap->unmap(handle->unmap->handle, obj->body.id);
293 								else
294 									uri = "Unknown";
295 							}
296 							else // not an object
297 							{
298 								uri = handle->unmap->unmap(handle->unmap->handle, body->type);
299 							}
300 
301 							const float entry [4] = {0.1, 0.65, 0.15, 0.1};
302 							nk_layout_row(ctx, NK_DYNAMIC, widget_h, 4, entry);
303 							{
304 								_shadow(ctx, &handle->shadow);
305 								nk_labelf_colored(ctx, NK_TEXT_LEFT, yellow, "+%04"PRIi64, frames);
306 
307 								if(nk_select_label(ctx, uri, NK_TEXT_LEFT, handle->selected == body))
308 								{
309 									handle->ttl_dirty = handle->ttl_dirty
310 										|| (handle->selected != body); // has selection actually changed?
311 									handle->selected = body;
312 								}
313 
314 								if(body->type == handle->forge.Bool)
315 								{
316 									const LV2_Atom_Bool *ref = (const LV2_Atom_Bool *)body;
317 									nk_labelf_colored(ctx, NK_TEXT_RIGHT, violet, "%s", ref->body ? "true" : "false");
318 								}
319 								else if(body->type == handle->forge.Int)
320 								{
321 									const LV2_Atom_Int *ref = (const LV2_Atom_Int *)body;
322 									nk_labelf_colored(ctx, NK_TEXT_RIGHT, green, "%"PRIi32, ref->body);
323 								}
324 								else if(body->type == handle->forge.Long)
325 								{
326 									const LV2_Atom_Long *ref = (const LV2_Atom_Long *)body;
327 									nk_labelf_colored(ctx, NK_TEXT_RIGHT, green, "%"PRIi64, ref->body);
328 								}
329 								else if(body->type == handle->forge.Float)
330 								{
331 									const LV2_Atom_Float *ref = (const LV2_Atom_Float *)body;
332 									nk_labelf_colored(ctx, NK_TEXT_RIGHT, green, "%f", ref->body);
333 								}
334 								else if(body->type == handle->forge.Double)
335 								{
336 									const LV2_Atom_Double *ref = (const LV2_Atom_Double *)body;
337 									nk_labelf_colored(ctx, NK_TEXT_RIGHT, green, "%lf", ref->body);
338 								}
339 								/*
340 								else if(body->type == handle->forge.String)
341 								{
342 									nk_label_colored(ctx, LV2_ATOM_BODY_CONST(body), NK_TEXT_RIGHT, red);
343 								}
344 								else if(body->type == handle->forge.URI)
345 								{
346 									nk_label_colored(ctx, LV2_ATOM_BODY_CONST(body), NK_TEXT_RIGHT, yellow);
347 								}
348 								else if(body->type == handle->forge.URID)
349 								{
350 									const LV2_Atom_URID *urid = (const LV2_Atom_URID *)body;
351 									const char *_uri = handle->unmap->unmap(handle->unmap->handle, urid->body);
352 									nk_label_colored(ctx, _uri, NK_TEXT_RIGHT, yellow);
353 								}
354 								else if(body->type == handle->forge.Path)
355 								{
356 									nk_label_colored(ctx, LV2_ATOM_BODY_CONST(body), NK_TEXT_RIGHT, red);
357 								}
358 								else if(body->type == handle->forge.Literal)
359 								{
360 									nk_label_colored(ctx, LV2_ATOM_CONTENTS_CONST(LV2_Atom_Literal, body), NK_TEXT_RIGHT, red);
361 								}
362 								*/
363 								else
364 								{
365 									nk_spacing(ctx, 1);
366 								}
367 
368 								nk_labelf_colored(ctx, NK_TEXT_RIGHT, blue, "%"PRIu32, body->size);
369 							}
370 						} break;
371 					}
372 				}
373 
374 				nk_list_view_end(&lview);
375 			}
376 
377 			const float n = 4;
378 			const float r0 = 1.f / n;
379 			const float r1 = 0.1f / 3;
380 			const float r2 = r0 - r1;
381 			const float footer [8] = {r1, r2, r1, r2, r1, r2, r1, r2};
382 			nk_layout_row(ctx, NK_DYNAMIC, widget_h, 8, footer);
383 			{
384 				const int32_t state_overwrite = _check(ctx, handle->state.overwrite);
385 				if(state_overwrite != handle->state.overwrite)
386 				{
387 					handle->state.overwrite = state_overwrite;
388 					_set_bool(handle, handle->urid.overwrite, handle->state.overwrite);
389 				}
390 				nk_label(ctx, "overwrite", NK_TEXT_LEFT);
391 
392 				const int32_t state_block = _check(ctx, handle->state.block);
393 				if(state_block != handle->state.block)
394 				{
395 					handle->state.block = state_block;
396 					_set_bool(handle, handle->urid.block, handle->state.block);
397 				}
398 				nk_label(ctx, "block", NK_TEXT_LEFT);
399 
400 				const int32_t state_follow = _check(ctx, handle->state.follow);
401 				if(state_follow != handle->state.follow)
402 				{
403 					handle->state.follow = state_follow;
404 					_set_bool(handle, handle->urid.follow, handle->state.follow);
405 				}
406 				nk_label(ctx, "follow", NK_TEXT_LEFT);
407 
408 				const int32_t state_pretty = _check(ctx, handle->state.pretty);
409 				if(state_pretty != handle->state.pretty)
410 				{
411 					handle->state.pretty = state_pretty;
412 					_set_bool(handle, handle->urid.pretty, handle->state.pretty);
413 
414 					handle->ttl_dirty = true;
415 				}
416 				nk_label(ctx, "pretty", NK_TEXT_LEFT);
417 			}
418 
419 			const bool max_reached = handle->n_item >= MAX_LINES;
420 			nk_layout_row_dynamic(ctx, widget_h, 2);
421 			if(nk_button_symbol_label(ctx,
422 				max_reached ? NK_SYMBOL_TRIANGLE_RIGHT: NK_SYMBOL_NONE,
423 				"clear", NK_TEXT_LEFT))
424 			{
425 				_clear(handle);
426 			}
427 			nk_label(ctx, "Sherlock.lv2: "SHERLOCK_VERSION, NK_TEXT_RIGHT);
428 
429 			nk_group_end(ctx);
430 		}
431 
432 		if(nk_group_begin(ctx, "Right", NK_WINDOW_NO_SCROLLBAR))
433 		{
434 			const LV2_Atom *atom = handle->selected;
435 			if(handle->ttl_dirty && atom)
436 			{
437 				sratom_set_pretty_numbers(handle->sratom, handle->state.pretty);
438 
439 				char *ttl = _sratom_to_turtle(handle->sratom, handle->unmap,
440 					handle->base_uri, NULL, NULL,
441 					atom->type, atom->size, LV2_ATOM_BODY_CONST(atom));
442 				if(ttl)
443 				{
444 					struct nk_str *str = &handle->editor.string;
445 					const size_t len = strlen(ttl);
446 
447 					_set_string(str, len, ttl);
448 
449 					handle->editor.lexer.needs_refresh = 1;
450 
451 					free(ttl);
452 				}
453 
454 				handle->ttl_dirty = false;
455 			}
456 
457 			const nk_flags flags = NK_EDIT_BOX;
458 			int len = nk_str_len(&handle->editor.string);
459 
460 			if(len > 0) //FIXME
461 			{
462 				const float content_h = nk_window_get_height(ctx) - 2*window_padding.y - 2*group_padding.y;
463 				nk_layout_row_dynamic(ctx, content_h, 1);
464 				const nk_flags mode = nk_edit_buffer(ctx, flags, &handle->editor, nk_filter_default);
465 				(void)mode;
466 			}
467 
468 			nk_group_end(ctx);
469 		}
470 	}
471 	nk_end(ctx);
472 }
473