Root/
Source at commit 15e5811aea59345983f4f34454e11ee31811116a created 9 years 2 months ago. By Werner Almesberger, gui_frame.c (loop_scroll_event): reverse direction | |
---|---|
1 | /* |
2 | * gui_frame.c - GUI, frame window |
3 | * |
4 | * Written 2009, 2010, 2012, 2015 by Werner Almesberger |
5 | * Copyright 2009, 2010, 2012, 2015 by Werner Almesberger |
6 | * |
7 | * This program is free software; you can redistribute it and/or modify |
8 | * it under the terms of the GNU General Public License as published by |
9 | * the Free Software Foundation; either version 2 of the License, or |
10 | * (at your option) any later version. |
11 | */ |
12 | |
13 | |
14 | #include <string.h> |
15 | #include <gtk/gtk.h> |
16 | |
17 | #include "util.h" |
18 | #include "error.h" |
19 | #include "dump.h" |
20 | #include "inst.h" |
21 | #include "obj.h" |
22 | #include "delete.h" |
23 | #include "unparse.h" |
24 | #include "gui_util.h" |
25 | #include "gui_style.h" |
26 | #include "gui_status.h" |
27 | #include "gui_tool.h" |
28 | #include "gui_canvas.h" |
29 | #include "gui.h" |
30 | #include "gui_frame_drag.h" |
31 | #include "gui_frame.h" |
32 | |
33 | |
34 | enum sidebar sidebar = sidebar_var; |
35 | |
36 | |
37 | /* ----- add elements, shared ---------------------------------------------- */ |
38 | |
39 | |
40 | /* @@@ merge with fpd.y */ |
41 | |
42 | static void add_table(struct frame *frame, struct table **anchor) |
43 | { |
44 | struct table *table, **walk; |
45 | |
46 | table = zalloc_type(struct table); |
47 | table->vars = zalloc_type(struct var); |
48 | table->vars->name = unique("_"); |
49 | table->vars->frame = frame; |
50 | table->vars->table = table; |
51 | table->rows = zalloc_type(struct row); |
52 | table->rows->table = table; |
53 | table->rows->values = zalloc_type(struct value); |
54 | table->rows->values->expr = parse_expr("0"); |
55 | table->rows->values->row = table->rows; |
56 | table->active_row = table->rows; |
57 | if (anchor) { |
58 | table->next = *anchor; |
59 | *anchor = table; |
60 | } else { |
61 | for (walk = &frame->tables; *walk; walk = &(*walk)->next); |
62 | *walk = table; |
63 | } |
64 | change_world(); |
65 | } |
66 | |
67 | |
68 | static void add_loop(struct frame *frame, struct loop **anchor) |
69 | { |
70 | struct loop *loop, **walk; |
71 | |
72 | loop = zalloc_type(struct loop); |
73 | loop->var.name = unique("_"); |
74 | loop->var.frame = frame; |
75 | loop->from.expr = parse_expr("0"); |
76 | loop->to.expr = parse_expr("0"); |
77 | if (anchor) { |
78 | loop->next = *anchor; |
79 | *anchor = loop; |
80 | } else { |
81 | loop->next = NULL; |
82 | for (walk = &frame->loops; *walk; walk = &(*walk)->next); |
83 | *walk = loop; |
84 | } |
85 | change_world(); |
86 | } |
87 | |
88 | |
89 | /* ----- popup dispatcher -------------------------------------------------- */ |
90 | |
91 | |
92 | static void *popup_data; |
93 | |
94 | |
95 | static void pop_up(GtkWidget *menu, GdkEventButton *event, void *data) |
96 | { |
97 | popup_data = data; |
98 | gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, |
99 | event->button, event->time); |
100 | } |
101 | |
102 | |
103 | /* ----- popup: frame ------------------------------------------------------ */ |
104 | |
105 | |
106 | static GtkItemFactory *factory_frame; |
107 | static GtkWidget *popup_frame_widget; |
108 | |
109 | |
110 | static void popup_add_frame(void) |
111 | { |
112 | struct frame *parent = popup_data; |
113 | struct frame *new; |
114 | |
115 | new = zalloc_type(struct frame); |
116 | new->name = unique("_"); |
117 | new->next = parent->next; |
118 | parent->next = new; |
119 | change_world(); |
120 | } |
121 | |
122 | |
123 | static void popup_del_frame(void) |
124 | { |
125 | struct frame *frame = popup_data; |
126 | |
127 | assert(frame != frames); |
128 | delete_frame(frame); |
129 | if (active_frame == frame) |
130 | select_frame(frames); |
131 | change_world(); |
132 | } |
133 | |
134 | |
135 | static void popup_add_table(void) |
136 | { |
137 | add_table(popup_data, NULL); |
138 | } |
139 | |
140 | |
141 | static void popup_add_loop(void) |
142 | { |
143 | add_loop(popup_data, NULL); |
144 | } |
145 | |
146 | |
147 | static GtkItemFactoryEntry popup_frame_entries[] = { |
148 | { "/Add frame", NULL, popup_add_frame,0, "<Item>" }, |
149 | { "/sep0", NULL, NULL, 0, "<Separator>" }, |
150 | { "/Add variable", NULL, popup_add_table,0, "<Item>" }, |
151 | { "/Add loop", NULL, popup_add_loop, 0, "<Item>" }, |
152 | { "/sep1", NULL, NULL, 0, "<Separator>" }, |
153 | { "/Delete frame", NULL, popup_del_frame,0, "<Item>" }, |
154 | { "/sep2", NULL, NULL, 0, "<Separator>" }, |
155 | { "/Close", NULL, NULL, 0, "<Item>" }, |
156 | { NULL } |
157 | }; |
158 | |
159 | |
160 | static gboolean can_add_frame(void) |
161 | { |
162 | const struct frame *frame; |
163 | |
164 | for (frame = frames->next; frame; frame = frame->next) |
165 | if (!strcmp(frame->name, "_")) |
166 | return FALSE; |
167 | return TRUE; |
168 | } |
169 | |
170 | |
171 | static gboolean can_add_var(const struct frame *frame) |
172 | { |
173 | const struct table *table; |
174 | const struct var *var; |
175 | const struct loop *loop; |
176 | |
177 | for (table = frame->tables; table; table = table->next) |
178 | for (var = table->vars; var; var = var->next) |
179 | if (!strcmp(var->name, "_")) |
180 | return FALSE; |
181 | for (loop = frame->loops; loop; loop = loop->next) |
182 | if (!strcmp(loop->var.name, "_")) |
183 | return FALSE; |
184 | return TRUE; |
185 | } |
186 | |
187 | |
188 | static void enable_add_var(struct frame *frame, GtkItemFactory *factory) |
189 | { |
190 | gboolean add_var; |
191 | |
192 | add_var = can_add_var(frame); |
193 | gtk_widget_set_sensitive( |
194 | gtk_item_factory_get_item(factory, "/Add variable"), add_var); |
195 | gtk_widget_set_sensitive( |
196 | gtk_item_factory_get_item(factory, "/Add loop"), add_var); |
197 | } |
198 | |
199 | |
200 | static void pop_up_frame(struct frame *frame, GdkEventButton *event) |
201 | { |
202 | gtk_widget_set_sensitive( |
203 | gtk_item_factory_get_item(factory_frame, "/Delete frame"), |
204 | frame != frames); |
205 | |
206 | gtk_widget_set_sensitive( |
207 | gtk_item_factory_get_item(factory_frame, "/Add frame"), |
208 | can_add_frame()); |
209 | |
210 | enable_add_var(frame, factory_frame); |
211 | |
212 | pop_up(popup_frame_widget, event, frame); |
213 | } |
214 | |
215 | |
216 | /* ----- popup: single variable -------------------------------------------- */ |
217 | |
218 | |
219 | static GtkItemFactory *factory_single_var; |
220 | static GtkWidget *popup_single_var_widget; |
221 | |
222 | |
223 | static void add_row_here(struct table *table, struct row **anchor) |
224 | { |
225 | struct row *row; |
226 | const struct value *walk; |
227 | struct value *value; |
228 | |
229 | row = zalloc_type(struct row); |
230 | row->table = table; |
231 | /* @@@ future: adjust type */ |
232 | for (walk = table->rows->values; walk; walk = walk->next) { |
233 | value = zalloc_type(struct value); |
234 | value->expr = parse_expr("0"); |
235 | value->row = row; |
236 | value->next = row->values; |
237 | row->values = value; |
238 | } |
239 | row->next = *anchor; |
240 | *anchor = row; |
241 | change_world(); |
242 | } |
243 | |
244 | |
245 | static void add_column_here(struct table *table, struct var **anchor) |
246 | { |
247 | const struct var *walk; |
248 | struct var *var; |
249 | struct row *row; |
250 | struct value *value; |
251 | struct value **value_anchor; |
252 | int n = 0, i; |
253 | |
254 | for (walk = table->vars; walk != *anchor; walk = walk->next) |
255 | n++; |
256 | var = zalloc_type(struct var); |
257 | var->name = unique("_"); |
258 | var->frame = table->vars->frame; |
259 | var->table = table; |
260 | var->next = *anchor; |
261 | *anchor = var; |
262 | for (row = table->rows; row; row = row->next) { |
263 | value_anchor = &row->values; |
264 | for (i = 0; i != n; i++) |
265 | value_anchor = &(*value_anchor)->next; |
266 | value = zalloc_type(struct value); |
267 | value->expr = parse_expr("0"); |
268 | value->row = row; |
269 | value->next = *value_anchor; |
270 | *value_anchor = value; |
271 | } |
272 | change_world(); |
273 | } |
274 | |
275 | |
276 | static void popup_add_row(void) |
277 | { |
278 | struct var *var = popup_data; |
279 | |
280 | add_row_here(var->table, &var->table->rows); |
281 | } |
282 | |
283 | |
284 | static void popup_add_column(void) |
285 | { |
286 | struct var *var = popup_data; |
287 | |
288 | add_column_here(var->table, &var->next); |
289 | } |
290 | |
291 | |
292 | static void popup_del_table(void) |
293 | { |
294 | struct var *var = popup_data; |
295 | |
296 | delete_table(var->table); |
297 | change_world(); |
298 | } |
299 | |
300 | |
301 | static void popup_add_table_from_var(void) |
302 | { |
303 | struct var *var = popup_data; |
304 | |
305 | add_table(var->frame, &var->table->next); |
306 | } |
307 | |
308 | |
309 | static void popup_add_loop_from_var(void) |
310 | { |
311 | struct var *var = popup_data; |
312 | |
313 | add_loop(var->frame, NULL); |
314 | } |
315 | |
316 | |
317 | static GtkItemFactoryEntry popup_single_var_entries[] = { |
318 | { "/Add row", NULL, popup_add_row, 0, "<Item>" }, |
319 | { "/Add column", NULL, popup_add_column, 0, "<Item>" }, |
320 | { "/sep1", NULL, NULL, 0, "<Separator>" }, |
321 | { "/Delete variable", NULL, popup_del_table,0, "<Item>" }, |
322 | { "/sep2", NULL, NULL, 0, "<Separator>" }, |
323 | { "/Add variable", NULL, popup_add_table_from_var, |
324 | 0, "<Item>" }, |
325 | { "/Add loop", NULL, popup_add_loop_from_var, |
326 | 0, "<Item>" }, |
327 | { "/sep3", NULL, NULL, 0, "<Separator>" }, |
328 | { "/Close", NULL, NULL, 0, "<Item>" }, |
329 | { NULL } |
330 | }; |
331 | |
332 | |
333 | static void pop_up_single_var(struct var *var, GdkEventButton *event) |
334 | { |
335 | gtk_widget_set_sensitive( |
336 | gtk_item_factory_get_item(factory_single_var, "/Add column"), |
337 | can_add_var(var->frame)); |
338 | enable_add_var(var->frame, factory_single_var); |
339 | pop_up(popup_single_var_widget, event, var); |
340 | } |
341 | |
342 | |
343 | /* ----- popup: table variable --------------------------------------------- */ |
344 | |
345 | |
346 | static GtkItemFactory *factory_table_var; |
347 | static GtkWidget *popup_table_var_widget; |
348 | |
349 | |
350 | static void popup_del_column(void) |
351 | { |
352 | struct var *var = popup_data; |
353 | const struct var *walk; |
354 | int n = 0; |
355 | |
356 | for (walk = var->table->vars; walk != var; walk = walk->next) |
357 | n++; |
358 | delete_column(var->table, n); |
359 | change_world(); |
360 | } |
361 | |
362 | |
363 | static GtkItemFactoryEntry popup_table_var_entries[] = { |
364 | { "/Add row", NULL, popup_add_row, 0, "<Item>" }, |
365 | { "/Add column", NULL, popup_add_column, 0, "<Item>" }, |
366 | { "/sep1", NULL, NULL, 0, "<Separator>" }, |
367 | { "/Delete table", NULL, popup_del_table,0, "<Item>" }, |
368 | { "/Delete column", NULL, popup_del_column, 0, "<Item>" }, |
369 | { "/sep2", NULL, NULL, 0, "<Separator>" }, |
370 | { "/Add variable", NULL, popup_add_table_from_var, |
371 | 0, "<Item>" }, |
372 | { "/Add loop", NULL, popup_add_loop_from_var, |
373 | 0, "<Item>" }, |
374 | { "/sep3", NULL, NULL, 0, "<Separator>" }, |
375 | { "/Close", NULL, NULL, 0, "<Item>" }, |
376 | { NULL } |
377 | }; |
378 | |
379 | |
380 | static void pop_up_table_var(struct var *var, GdkEventButton *event) |
381 | { |
382 | gtk_widget_set_sensitive( |
383 | gtk_item_factory_get_item(factory_table_var, "/Delete column"), |
384 | var->table->vars->next != NULL); |
385 | gtk_widget_set_sensitive( |
386 | gtk_item_factory_get_item(factory_table_var, "/Add column"), |
387 | can_add_var(var->frame)); |
388 | enable_add_var(var->frame, factory_table_var); |
389 | pop_up(popup_table_var_widget, event, var); |
390 | } |
391 | |
392 | |
393 | /* ----- popup: table value ------------------------------------------------ */ |
394 | |
395 | |
396 | static GtkItemFactory *factory_table_value; |
397 | static GtkWidget *popup_table_value_widget; |
398 | |
399 | |
400 | static void popup_add_column_by_value(void) |
401 | { |
402 | struct value *value = popup_data; |
403 | const struct value *walk; |
404 | struct table *table = value->row->table; |
405 | struct var *var = table->vars; |
406 | |
407 | for (walk = value->row->values; walk != value; walk = walk->next) |
408 | var = var->next; |
409 | add_column_here(table, &var->next); |
410 | } |
411 | |
412 | |
413 | static void popup_add_row_by_value(void) |
414 | { |
415 | struct value *value = popup_data; |
416 | |
417 | add_row_here(value->row->table, &value->row->next); |
418 | } |
419 | |
420 | |
421 | static void popup_del_row(void) |
422 | { |
423 | struct value *value = popup_data; |
424 | struct table *table = value->row->table; |
425 | |
426 | delete_row(value->row); |
427 | if (table->active_row == value->row) |
428 | table->active_row = table->rows; |
429 | change_world(); |
430 | } |
431 | |
432 | |
433 | static void popup_del_column_by_value(void) |
434 | { |
435 | struct value *value = popup_data; |
436 | const struct value *walk; |
437 | int n = 0; |
438 | |
439 | for (walk = value->row->values; walk != value; walk = walk->next) |
440 | n++; |
441 | delete_column(value->row->table, n); |
442 | change_world(); |
443 | } |
444 | |
445 | |
446 | static GtkItemFactoryEntry popup_table_value_entries[] = { |
447 | { "/Add row", NULL, popup_add_row_by_value, 0, "<Item>" }, |
448 | { "/Add column", NULL, popup_add_column_by_value, |
449 | 0, "<Item>" }, |
450 | { "/sep1", NULL, NULL, 0, "<Separator>" }, |
451 | { "/Delete row", NULL, popup_del_row, 0, "<Item>" }, |
452 | { "/Delete column", NULL, popup_del_column_by_value, |
453 | 0, "<Item>" }, |
454 | { "/sep2", NULL, NULL, 0, "<Separator>" }, |
455 | { "/Close", NULL, NULL, 0, "<Item>" }, |
456 | { NULL } |
457 | }; |
458 | |
459 | |
460 | static void pop_up_table_value(struct value *value, GdkEventButton *event) |
461 | { |
462 | gtk_widget_set_sensitive( |
463 | gtk_item_factory_get_item(factory_table_value, "/Delete row"), |
464 | value->row->table->rows->next != NULL); |
465 | gtk_widget_set_sensitive( |
466 | gtk_item_factory_get_item(factory_table_value, "/Delete column"), |
467 | value->row->table->vars->next != NULL); |
468 | pop_up(popup_table_value_widget, event, value); |
469 | } |
470 | |
471 | |
472 | /* ----- popup: loop ------------------------------------------------------- */ |
473 | |
474 | |
475 | static GtkItemFactory *factory_loop_var; |
476 | static GtkWidget *popup_loop_var_widget; |
477 | |
478 | |
479 | static void popup_del_loop(void) |
480 | { |
481 | struct loop *loop = popup_data; |
482 | |
483 | delete_loop(loop); |
484 | change_world(); |
485 | } |
486 | |
487 | |
488 | static void popup_add_table_from_loop(void) |
489 | { |
490 | struct loop *loop = popup_data; |
491 | |
492 | add_table(loop->var.frame, NULL); |
493 | } |
494 | |
495 | |
496 | static void popup_add_loop_from_loop(void) |
497 | { |
498 | struct loop *loop = popup_data; |
499 | |
500 | add_loop(loop->var.frame, &loop->next); |
501 | } |
502 | |
503 | |
504 | static GtkItemFactoryEntry popup_loop_var_entries[] = { |
505 | { "/Delete loop", NULL, popup_del_loop, 0, "<Item>" }, |
506 | { "/sep1", NULL, NULL, 0, "<Separator>" }, |
507 | { "/Add variable", NULL, popup_add_table_from_loop, |
508 | 0, "<Item>" }, |
509 | { "/Add loop", NULL, popup_add_loop_from_loop, |
510 | 0, "<Item>" }, |
511 | { "/sep2", NULL, NULL, 0, "<Separator>" }, |
512 | { "/Close", NULL, NULL, 0, "<Item>" }, |
513 | { NULL } |
514 | }; |
515 | |
516 | |
517 | static void pop_up_loop_var(struct loop *loop, GdkEventButton *event) |
518 | { |
519 | enable_add_var(loop->var.frame, factory_loop_var); |
520 | pop_up(popup_loop_var_widget, event, loop); |
521 | } |
522 | |
523 | |
524 | /* ----- make popups ------------------------------------------------------- */ |
525 | |
526 | |
527 | static GtkWidget *make_popup(const char *name, GtkItemFactory **factory, |
528 | GtkItemFactoryEntry *entries) |
529 | { |
530 | GtkWidget *popup; |
531 | int n; |
532 | |
533 | n = 0; |
534 | for (n = 0; entries[n].path; n++); |
535 | |
536 | *factory = gtk_item_factory_new(GTK_TYPE_MENU, name, NULL); |
537 | gtk_item_factory_create_items(*factory, n, entries, NULL); |
538 | popup = gtk_item_factory_get_widget(*factory, name); |
539 | return popup; |
540 | } |
541 | |
542 | |
543 | void make_popups(void) |
544 | { |
545 | popup_frame_widget = make_popup("<FpedFramePopUp>", |
546 | &factory_frame, popup_frame_entries); |
547 | popup_single_var_widget = make_popup("<FpedSingleVarPopUp>", |
548 | &factory_single_var, popup_single_var_entries); |
549 | popup_table_var_widget = make_popup("<FpedTableVarPopUp>", |
550 | &factory_table_var, popup_table_var_entries); |
551 | popup_table_value_widget = make_popup("<FpedTableValusPopUp>", |
552 | &factory_table_value, popup_table_value_entries); |
553 | popup_loop_var_widget = make_popup("<FpedLoopVarPopUp>", |
554 | &factory_loop_var, popup_loop_var_entries); |
555 | } |
556 | |
557 | |
558 | /* ----- variable list ----------------------------------------------------- */ |
559 | |
560 | |
561 | static void add_sep(GtkWidget *box, int size) |
562 | { |
563 | GtkWidget *sep; |
564 | GdkColor black = { 0, 0, 0, 0 }; |
565 | |
566 | sep = gtk_drawing_area_new(); |
567 | gtk_box_pack_start(GTK_BOX(box), sep, FALSE, TRUE, size); |
568 | gtk_widget_modify_bg(sep, GTK_STATE_NORMAL, &black); |
569 | } |
570 | |
571 | |
572 | /* ----- variable name editor ---------------------------------------------- */ |
573 | |
574 | |
575 | static int find_var_in_frame(const struct frame *frame, const char *name, |
576 | const struct var *self) |
577 | { |
578 | const struct table *table; |
579 | const struct loop *loop; |
580 | const struct var *var; |
581 | |
582 | for (table = frame->tables; table; table = table->next) |
583 | for (var = table->vars; var; var = var->next) |
584 | if (var != self && !var->key && |
585 | !strcmp(var->name, name)) |
586 | return 1; |
587 | for (loop = frame->loops; loop; loop = loop->next) |
588 | if (&loop->var != self && !strcmp(loop->var.name, name)) |
589 | return 1; |
590 | return 0; |
591 | } |
592 | |
593 | |
594 | static int validate_var_name(const char *s, void *ctx) |
595 | { |
596 | struct var *var = ctx; |
597 | |
598 | if (!is_id(s)) |
599 | return 0; |
600 | if (var->key) |
601 | return 1; |
602 | return !find_var_in_frame(var->frame, s, var); |
603 | } |
604 | |
605 | |
606 | static void unselect_var(void *data) |
607 | { |
608 | struct var *var = data; |
609 | |
610 | label_in_box_bg(var->widget, COLOR_VAR_PASSIVE); |
611 | } |
612 | |
613 | |
614 | static void show_value(const struct expr *expr, const struct frame *frame) |
615 | { |
616 | const char *value_string; |
617 | struct num value; |
618 | |
619 | status_set_type_x(NULL, "value ="); |
620 | value_string = eval_str(expr, frame); |
621 | if (value_string) { |
622 | status_set_x(NULL, "\"%s\"", value_string); |
623 | } else { |
624 | value = eval_num(expr, frame); |
625 | if (is_undef(value)) |
626 | status_set_x(NULL, "undefined"); |
627 | else |
628 | status_set_x(NULL, "%lg%s", value.n, str_unit(value)); |
629 | } |
630 | } |
631 | |
632 | |
633 | static void show_var_value(const struct var *var, const struct frame *frame) |
634 | { |
635 | const struct var *walk; |
636 | const struct value *value; |
637 | |
638 | if (!var->table) |
639 | return; |
640 | value = var->table->active_row->values; |
641 | for (walk = var->table->vars; walk != var; walk = walk->next) |
642 | value = value->next; |
643 | show_value(value->expr, frame); |
644 | } |
645 | |
646 | |
647 | static void edit_var(struct var *var, |
648 | void (*set_values)(void *user, const struct value *values, int n_values), |
649 | void *user, int max_values) |
650 | { |
651 | inst_select_outside(var, unselect_var); |
652 | label_in_box_bg(var->widget, COLOR_VAR_EDITING); |
653 | status_set_type_entry(NULL, "name ="); |
654 | status_set_name("Variable name", "%s", var->name); |
655 | show_var_value(var, var->frame); |
656 | edit_nothing(); |
657 | edit_var_type(var); |
658 | edit_unique_with_values(&var->name, validate_var_name, var, |
659 | set_values, user, max_values, |
660 | "Variable name. " |
661 | "Shortcut:<b><i>name</i>=<i>value</i>,<i>...</i> </b>"); |
662 | } |
663 | |
664 | |
665 | static void set_col_values(void *user, const struct value *values, |
666 | int n_values); |
667 | |
668 | |
669 | void reselect_var(struct var *var) |
670 | { |
671 | edit_var(var, set_col_values, var, -1); |
672 | } |
673 | |
674 | |
675 | /* ----- value editor ------------------------------------------------------ */ |
676 | |
677 | |
678 | static void unselect_value(void *data) |
679 | { |
680 | struct value *value = data; |
681 | |
682 | /* |
683 | * This condition is a little cryptic. Here is what it does: |
684 | * |
685 | * IF table/assignment (not loop) |
686 | * AND the current row is the active (selected) row |
687 | * AND it's an assignment (not a table). |
688 | * |
689 | * We need the last condition because the expressions of assignments |
690 | * are drawn with COLOR_EXPR_PASSIVE. (See build_assignment.) |
691 | */ |
692 | label_in_box_bg(value->widget, |
693 | value->row && value->row->table->active_row == value->row && |
694 | (value->row->table->rows->next || value->row->table->vars->next) ? |
695 | COLOR_CHOICE_SELECTED : COLOR_EXPR_PASSIVE); |
696 | } |
697 | |
698 | |
699 | static void edit_value(struct value *value, const struct frame *frame) |
700 | { |
701 | inst_select_outside(value, unselect_value); |
702 | label_in_box_bg(value->widget, COLOR_EXPR_EDITING); |
703 | show_value(value->expr, frame); |
704 | edit_nothing(); |
705 | edit_expr(&value->expr, "Value"); |
706 | } |
707 | |
708 | |
709 | static void edit_value_list(struct value *value, const struct frame *frame, |
710 | void (*set_values)(void *user, const struct value *values, int n_values), |
711 | void *user) |
712 | { |
713 | inst_select_outside(value, unselect_value); |
714 | label_in_box_bg(value->widget, COLOR_EXPR_EDITING); |
715 | show_value(value->expr, frame); |
716 | edit_nothing(); |
717 | edit_expr_list(value->expr, set_values, user, "Value(s)"); |
718 | } |
719 | |
720 | |
721 | /* ----- activator --------------------------------------------------------- */ |
722 | |
723 | |
724 | static GtkWidget *add_activator(GtkWidget *hbox, int active, |
725 | gboolean (*cb)(GtkWidget *widget, GdkEventButton *event, gpointer data), |
726 | gpointer user, const char *tooltip, const char *fmt, ...) |
727 | { |
728 | GtkWidget *label; |
729 | va_list ap; |
730 | char buf[100]; |
731 | |
732 | va_start(ap, fmt); |
733 | vsprintf(buf, fmt, ap); |
734 | va_end(ap); |
735 | label = label_in_box_new(buf, tooltip); |
736 | gtk_misc_set_padding(GTK_MISC(label), 2, 2); |
737 | gtk_misc_set_alignment(GTK_MISC(label), 0, 0); |
738 | label_in_box_bg(label, |
739 | active ? COLOR_CHOICE_SELECTED : COLOR_CHOICE_UNSELECTED); |
740 | gtk_box_pack_start(GTK_BOX(hbox), box_of_label(label), |
741 | FALSE, FALSE, 2); |
742 | g_signal_connect(G_OBJECT(box_of_label(label)), |
743 | "button_press_event", G_CALLBACK(cb), user); |
744 | return label; |
745 | } |
746 | |
747 | |
748 | /* ----- assignments ------------------------------------------------------- */ |
749 | |
750 | |
751 | static void set_col_values(void *user, const struct value *values, |
752 | int n_values) |
753 | { |
754 | struct var *var = user; |
755 | struct table *table = var->table; |
756 | struct value *value; |
757 | const struct var *walk; |
758 | struct row **row; |
759 | |
760 | row = &table->rows; |
761 | while (values) { |
762 | if (!*row) |
763 | add_row_here(table, row); |
764 | value = (*row)->values; |
765 | for (walk = table->vars; walk != var; walk = walk->next) |
766 | value = value->next; |
767 | free_expr(value->expr); |
768 | value->expr = values->expr; |
769 | values = values->next; |
770 | row = &(*row)->next; |
771 | } |
772 | } |
773 | |
774 | |
775 | static gboolean assignment_var_select_event(GtkWidget *widget, |
776 | GdkEventButton *event, gpointer data) |
777 | { |
778 | struct var *var = data; |
779 | |
780 | switch (event->button) { |
781 | case 1: |
782 | edit_var(var, set_col_values, var, -1); |
783 | break; |
784 | case 3: |
785 | pop_up_single_var(var, event); |
786 | break; |
787 | } |
788 | return TRUE; |
789 | } |
790 | |
791 | |
792 | static gboolean assignment_value_select_event(GtkWidget *widget, |
793 | GdkEventButton *event, gpointer data) |
794 | { |
795 | struct value *value = data; |
796 | |
797 | switch (event->button) { |
798 | case 1: |
799 | edit_nothing(); |
800 | edit_value(value, value->row->table->vars->frame); |
801 | break; |
802 | } |
803 | return TRUE; |
804 | } |
805 | |
806 | |
807 | /* |
808 | * In tables, expressions in the active row have a COLOR_CHOICE_SELECTED |
809 | * background. While expressions in assignments are technically on the active |
810 | * (and only) row, we use COLOR_VAR_PASSIVE for better readability. |
811 | */ |
812 | |
813 | static void build_assignment(GtkWidget *vbox, struct frame *frame, |
814 | struct table *table) |
815 | { |
816 | GtkWidget *hbox, *field; |
817 | char *name, *expr; |
818 | |
819 | if (!table->vars || table->vars->next) |
820 | return; |
821 | if (!table->rows || table->rows->next) |
822 | return; |
823 | |
824 | hbox = gtk_hbox_new(FALSE, 0); |
825 | gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); |
826 | |
827 | name = stralloc_printf("%s%s", table->vars->key ? "?" : "", |
828 | table->vars->name); |
829 | field = label_in_box_new(name, "Variable name. Click to edit."); |
830 | free(name); |
831 | |
832 | gtk_box_pack_start(GTK_BOX(hbox), box_of_label(field), FALSE, FALSE, 0); |
833 | label_in_box_bg(field, COLOR_VAR_PASSIVE); |
834 | table->vars->widget = field; |
835 | g_signal_connect(G_OBJECT(box_of_label(field)), |
836 | "button_press_event", |
837 | G_CALLBACK(assignment_var_select_event), table->vars); |
838 | |
839 | gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new(" = "), |
840 | FALSE, FALSE, 0); |
841 | |
842 | expr = unparse(table->rows->values->expr); |
843 | field = label_in_box_new(expr, "Variable value. Click to edit."); |
844 | free(expr); |
845 | gtk_box_pack_start(GTK_BOX(hbox), box_of_label(field), FALSE, FALSE, 0); |
846 | label_in_box_bg(field, COLOR_EXPR_PASSIVE); |
847 | table->rows->values->widget = field; |
848 | g_signal_connect(G_OBJECT(box_of_label(field)), |
849 | "button_press_event", |
850 | G_CALLBACK(assignment_value_select_event), table->rows->values); |
851 | } |
852 | |
853 | |
854 | /* ----- tables ------------------------------------------------------------ */ |
855 | |
856 | |
857 | static void select_row(struct row *row) |
858 | { |
859 | struct table *table = row->table; |
860 | struct value *value; |
861 | |
862 | for (value = table->active_row->values; value; value = value->next) |
863 | label_in_box_bg(value->widget, COLOR_ROW_UNSELECTED); |
864 | table->active_row = row; |
865 | for (value = table->active_row->values; value; value = value->next) |
866 | label_in_box_bg(value->widget, COLOR_ROW_SELECTED); |
867 | } |
868 | |
869 | |
870 | static void set_row_values(void *user, const struct value *values, |
871 | int n_values) |
872 | { |
873 | struct value *value = user; |
874 | struct row *row = value->row; |
875 | struct table *table = row->table; |
876 | struct var **var; |
877 | const struct value *walk; |
878 | int first = 1; |
879 | |
880 | var = &table->vars; |
881 | for (walk = row->values; walk != value; walk = walk->next) |
882 | var = &(*var)->next; |
883 | |
884 | while (values) { |
885 | if (!*var) |
886 | add_column_here(table, var); |
887 | if (first) |
888 | first = 0; |
889 | else |
890 | value = value->next; |
891 | free_expr(value->expr); |
892 | value->expr = values->expr; |
893 | values = values->next; |
894 | var = &(*var)->next; |
895 | } |
896 | } |
897 | |
898 | |
899 | static gboolean table_var_press_event(GtkWidget *widget, |
900 | GdkEventButton *event, gpointer data) |
901 | { |
902 | struct var *var = data; |
903 | |
904 | switch (event->button) { |
905 | case 3: |
906 | pop_up_table_var(var, event); |
907 | return TRUE; |
908 | } |
909 | return FALSE; |
910 | } |
911 | |
912 | |
913 | static gboolean table_var_release_event(GtkWidget *widget, |
914 | GdkEventButton *event, gpointer data) |
915 | { |
916 | struct var *var = data; |
917 | |
918 | switch (event->button) { |
919 | case 1: |
920 | if (is_dragging(var)) |
921 | return FALSE; |
922 | edit_var(var, set_col_values, var, -1); |
923 | return TRUE; |
924 | } |
925 | return FALSE; |
926 | } |
927 | |
928 | |
929 | static gboolean table_value_press_event(GtkWidget *widget, |
930 | GdkEventButton *event, gpointer data) |
931 | { |
932 | struct value *value = data; |
933 | |
934 | switch (event->button) { |
935 | case 3: |
936 | pop_up_table_value(value, event); |
937 | return TRUE; |
938 | } |
939 | return FALSE; |
940 | } |
941 | |
942 | |
943 | static gboolean table_value_release_event(GtkWidget *widget, |
944 | GdkEventButton *event, gpointer data) |
945 | { |
946 | struct value *value = data; |
947 | |
948 | switch (event->button) { |
949 | case 1: |
950 | if (is_dragging(value)) |
951 | return FALSE; |
952 | if (!value->row || |
953 | value->row->table->active_row == value->row) { |
954 | edit_nothing(); |
955 | edit_value_list(value, value->row->table->vars->frame, |
956 | set_row_values, value); |
957 | } else { |
958 | select_row(value->row); |
959 | change_world(); |
960 | } |
961 | return TRUE; |
962 | } |
963 | return FALSE; |
964 | } |
965 | |
966 | |
967 | static gboolean table_scroll_event(GtkWidget *widget, GdkEventScroll *event, |
968 | gpointer data) |
969 | { |
970 | struct table *table = data; |
971 | struct row *row, *last; |
972 | |
973 | switch (event->direction) { |
974 | case GDK_SCROLL_UP: |
975 | last = NULL; |
976 | for (row = table->rows; |
977 | row && (!last || row != table->active_row); row = row->next) |
978 | last = row; |
979 | table->active_row = last; |
980 | change_world(); |
981 | break; |
982 | case GDK_SCROLL_DOWN: |
983 | table->active_row = table->active_row->next; |
984 | if (!table->active_row) |
985 | table->active_row = table->rows; |
986 | change_world(); |
987 | break; |
988 | default: |
989 | /* ignore */; |
990 | } |
991 | return TRUE; |
992 | } |
993 | |
994 | |
995 | /* @@@ this function is too long */ |
996 | |
997 | static void build_table(GtkWidget *vbox, struct frame *frame, |
998 | struct table *table, int wrap_width) |
999 | { |
1000 | GtkWidget *tab, *field; |
1001 | GtkWidget *evbox, *align, *sep; |
1002 | struct var *var; |
1003 | struct row *row; |
1004 | struct value *value; |
1005 | int n_vars = 0, n_rows = 0; |
1006 | int n_var, n_row, pos; |
1007 | char *name, *expr; |
1008 | GdkColor color; |
1009 | |
1010 | for (var = table->vars; var; var = var->next) |
1011 | n_vars++; |
1012 | for (row = table->rows; row; row = row->next) |
1013 | n_rows++; |
1014 | |
1015 | if (n_vars == 1 && n_rows == 1) |
1016 | return; |
1017 | |
1018 | var = table->vars; |
1019 | n_var = 0; |
1020 | n_vars = 0; |
1021 | while (var) { |
1022 | if (n_vars) { |
1023 | gtk_table_resize(GTK_TABLE(tab), n_rows, n_vars+1); |
1024 | } else { |
1025 | evbox = gtk_event_box_new(); |
1026 | align = gtk_alignment_new(0, 0, 0, 0); |
1027 | gtk_container_add(GTK_CONTAINER(align), evbox); |
1028 | gtk_box_pack_start(GTK_BOX(vbox), align, |
1029 | FALSE, FALSE, 0); |
1030 | |
1031 | tab = gtk_table_new(n_rows+1, n_vars, FALSE); |
1032 | gtk_container_add(GTK_CONTAINER(evbox), tab); |
1033 | color = get_color(COLOR_VAR_TABLE_SEP); |
1034 | gtk_widget_modify_bg(GTK_WIDGET(evbox), |
1035 | GTK_STATE_NORMAL, &color); |
1036 | |
1037 | gtk_table_set_row_spacings(GTK_TABLE(tab), 1); |
1038 | gtk_table_set_col_spacings(GTK_TABLE(tab), 1); |
1039 | } |
1040 | |
1041 | name = stralloc_printf("%s%s", var->key ? "?" : "", var->name); |
1042 | field = label_in_box_new(name, |
1043 | "Variable (column) name. Click to edit."); |
1044 | free(name); |
1045 | |
1046 | gtk_table_attach_defaults(GTK_TABLE(tab), box_of_label(field), |
1047 | n_vars, n_vars+1, 0, 1); |
1048 | label_in_box_bg(field, COLOR_VAR_PASSIVE); |
1049 | g_signal_connect(G_OBJECT(box_of_label(field)), |
1050 | "button_press_event", |
1051 | G_CALLBACK(table_var_press_event), var); |
1052 | g_signal_connect(G_OBJECT(box_of_label(field)), |
1053 | "button_release_event", |
1054 | G_CALLBACK(table_var_release_event), var); |
1055 | g_signal_connect(G_OBJECT(box_of_label(field)), |
1056 | "scroll_event", |
1057 | G_CALLBACK(table_scroll_event), table); |
1058 | var->widget = field; |
1059 | |
1060 | setup_var_drag(var); |
1061 | |
1062 | n_row = 0; |
1063 | for (row = table->rows; row; row = row->next) { |
1064 | value = row->values; |
1065 | for (pos = 0; pos != n_var; pos++) |
1066 | value = value->next; |
1067 | expr = unparse(value->expr); |
1068 | field = label_in_box_new(expr, |
1069 | "Variable value. Click to select row or to edit."); |
1070 | free(expr); |
1071 | gtk_table_attach_defaults(GTK_TABLE(tab), |
1072 | box_of_label(field), |
1073 | n_vars, n_vars+1, |
1074 | n_row+1, n_row+2); |
1075 | label_in_box_bg(field, table->active_row == row ? |
1076 | COLOR_ROW_SELECTED : COLOR_ROW_UNSELECTED); |
1077 | g_signal_connect(G_OBJECT(box_of_label(field)), |
1078 | "button_press_event", |
1079 | G_CALLBACK(table_value_press_event), value); |
1080 | g_signal_connect(G_OBJECT(box_of_label(field)), |
1081 | "button_release_event", |
1082 | G_CALLBACK(table_value_release_event), value); |
1083 | g_signal_connect(G_OBJECT(box_of_label(field)), |
1084 | "scroll_event", |
1085 | G_CALLBACK(table_scroll_event), table); |
1086 | value->widget = field; |
1087 | setup_value_drag(value); |
1088 | n_row++; |
1089 | } |
1090 | |
1091 | /* |
1092 | * Wrap tables wider than the screen area available for |
1093 | * variables and tables. Don't wrap before having output at |
1094 | * least one column. |
1095 | */ |
1096 | if (n_vars && get_widget_width(tab) > wrap_width) { |
1097 | /* |
1098 | * Resizing alone doesn't hide extra columns. We have |
1099 | * to explicitly remove their content as well. |
1100 | */ |
1101 | gtk_container_remove(GTK_CONTAINER(tab), |
1102 | box_of_label(var->widget)); |
1103 | for (row = table->rows; row; row = row->next) { |
1104 | value = row->values; |
1105 | for (pos = 0; pos != n_var; pos++) |
1106 | value = value->next; |
1107 | gtk_container_remove(GTK_CONTAINER(tab), |
1108 | box_of_label(value->widget)); |
1109 | } |
1110 | gtk_table_resize(GTK_TABLE(tab), n_rows, n_vars); |
1111 | |
1112 | sep = gtk_vbox_new(FALSE, 0); |
1113 | gtk_box_pack_start(GTK_BOX(vbox), sep, |
1114 | FALSE, FALSE, 1); |
1115 | |
1116 | n_vars = 0; |
1117 | continue; |
1118 | } |
1119 | |
1120 | var = var->next; |
1121 | n_var++; |
1122 | n_vars++; |
1123 | } |
1124 | } |
1125 | |
1126 | |
1127 | /* ----- loops ------------------------------------------------------------- */ |
1128 | |
1129 | |
1130 | static void set_loop_values(void *user, const struct value *values, |
1131 | int n_values) |
1132 | { |
1133 | struct loop *loop = user; |
1134 | |
1135 | switch (n_values) { |
1136 | case 2: |
1137 | if (loop->to.expr) |
1138 | free_expr(loop->to.expr); |
1139 | loop->to.expr = values->next->expr; |
1140 | /* fall through */ |
1141 | case 1: |
1142 | if (loop->from.expr) |
1143 | free_expr(loop->from.expr); |
1144 | loop->from.expr = values->expr; |
1145 | break; |
1146 | case 0: |
1147 | break; |
1148 | default: |
1149 | abort(); |
1150 | } |
1151 | } |
1152 | |
1153 | |
1154 | static gboolean loop_var_select_event(GtkWidget *widget, |
1155 | GdkEventButton *event, gpointer data) |
1156 | { |
1157 | struct loop *loop = data; |
1158 | |
1159 | switch (event->button) { |
1160 | case 1: |
1161 | edit_var(&loop->var, set_loop_values, loop, 2); |
1162 | break; |
1163 | case 3: |
1164 | pop_up_loop_var(loop, event); |
1165 | break; |
1166 | } |
1167 | return TRUE; |
1168 | } |
1169 | |
1170 | |
1171 | static gboolean loop_from_select_event(GtkWidget *widget, |
1172 | GdkEventButton *event, gpointer data) |
1173 | { |
1174 | struct loop *loop = data; |
1175 | |
1176 | switch (event->button) { |
1177 | case 1: |
1178 | edit_nothing(); |
1179 | edit_value(&loop->from, loop->var.frame); |
1180 | break; |
1181 | } |
1182 | return TRUE; |
1183 | } |
1184 | |
1185 | |
1186 | static gboolean loop_to_select_event(GtkWidget *widget, |
1187 | GdkEventButton *event, gpointer data) |
1188 | { |
1189 | struct loop *loop = data; |
1190 | |
1191 | switch (event->button) { |
1192 | case 1: |
1193 | edit_nothing(); |
1194 | edit_value(&loop->to, loop->var.frame); |
1195 | break; |
1196 | } |
1197 | return TRUE; |
1198 | } |
1199 | |
1200 | |
1201 | static gboolean loop_select_event(GtkWidget *widget, GdkEventButton *event, |
1202 | gpointer data) |
1203 | { |
1204 | struct loop *loop = data; |
1205 | |
1206 | switch (event->button) { |
1207 | case 1: |
1208 | loop->active = |
1209 | (long) gtk_object_get_data(GTK_OBJECT(widget), "value"); |
1210 | change_world(); |
1211 | break; |
1212 | } |
1213 | return TRUE; |
1214 | } |
1215 | |
1216 | |
1217 | static gboolean loop_scroll_event(GtkWidget *widget, GdkEventScroll *event, |
1218 | gpointer data) |
1219 | { |
1220 | struct loop *loop = data; |
1221 | |
1222 | switch (event->direction) { |
1223 | case GDK_SCROLL_DOWN: |
1224 | if (loop->active < loop->iterations-1) { |
1225 | loop->active++; |
1226 | change_world(); |
1227 | } |
1228 | break; |
1229 | case GDK_SCROLL_UP: |
1230 | if (loop->active) { |
1231 | loop->active--; |
1232 | change_world(); |
1233 | } |
1234 | break; |
1235 | default: |
1236 | /* ignore */; |
1237 | } |
1238 | return TRUE; |
1239 | } |
1240 | |
1241 | |
1242 | static void build_loop(GtkWidget *vbox, struct frame *frame, |
1243 | struct loop *loop) |
1244 | { |
1245 | GtkWidget *hbox, *field, *label; |
1246 | char *expr; |
1247 | int i; |
1248 | |
1249 | hbox = gtk_hbox_new(FALSE, 0); |
1250 | gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); |
1251 | |
1252 | field = label_in_box_new(loop->var.name, |
1253 | "Variable name. Click to edit."); |
1254 | gtk_box_pack_start(GTK_BOX(hbox), box_of_label(field), FALSE, FALSE, 0); |
1255 | label_in_box_bg(field, COLOR_VAR_PASSIVE); |
1256 | if (instantiation_error == loop) |
1257 | label_in_box_fg(field, COLOR_ITEM_ERROR); |
1258 | g_signal_connect(G_OBJECT(box_of_label(field)), |
1259 | "button_press_event", |
1260 | G_CALLBACK(loop_var_select_event), loop); |
1261 | loop->var.widget = field; |
1262 | |
1263 | gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new(" = "), |
1264 | FALSE, FALSE, 0); |
1265 | |
1266 | expr = unparse(loop->from.expr); |
1267 | field = label_in_box_new(expr, |
1268 | "Start value of loop. Click to edit."); |
1269 | free(expr); |
1270 | gtk_box_pack_start(GTK_BOX(hbox), box_of_label(field), FALSE, FALSE, 0); |
1271 | label_in_box_bg(field, COLOR_EXPR_PASSIVE); |
1272 | g_signal_connect(G_OBJECT(box_of_label(field)), |
1273 | "button_press_event", |
1274 | G_CALLBACK(loop_from_select_event), loop); |
1275 | loop->from.widget = field; |
1276 | |
1277 | gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new(" ... "), |
1278 | FALSE, FALSE, 0); |
1279 | |
1280 | expr = unparse(loop->to.expr); |
1281 | field = label_in_box_new(expr, "End value of loop. Click to edit."); |
1282 | free(expr); |
1283 | gtk_box_pack_start(GTK_BOX(hbox), box_of_label(field), FALSE, FALSE, 0); |
1284 | label_in_box_bg(field, COLOR_EXPR_PASSIVE); |
1285 | g_signal_connect(G_OBJECT(box_of_label(field)), |
1286 | "button_press_event", |
1287 | G_CALLBACK(loop_to_select_event), loop); |
1288 | loop->to.widget = field; |
1289 | |
1290 | gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new(" ("), |
1291 | FALSE, FALSE, 0); |
1292 | |
1293 | for (i = 0; i != loop->iterations; i++) { |
1294 | label = add_activator(hbox, loop->active == i, |
1295 | loop_select_event, loop, |
1296 | "Loop value. Click to make active.", |
1297 | "%g", loop->n+i); |
1298 | gtk_object_set_data(GTK_OBJECT(box_of_label(label)), "value", |
1299 | (gpointer) (long) i); |
1300 | |
1301 | g_signal_connect(G_OBJECT(box_of_label(label)), |
1302 | "scroll_event", |
1303 | G_CALLBACK(loop_scroll_event), loop); |
1304 | } |
1305 | |
1306 | gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new(")"), |
1307 | FALSE, FALSE, 0); |
1308 | } |
1309 | |
1310 | |
1311 | /* ----- the list of variables, tables, and loops -------------------------- */ |
1312 | |
1313 | |
1314 | static GtkWidget *build_vars(struct frame *frame, int wrap_width) |
1315 | { |
1316 | GtkWidget *vbox; |
1317 | struct table *table; |
1318 | struct loop *loop; |
1319 | |
1320 | vbox = gtk_vbox_new(FALSE, 0); |
1321 | for (table = frame->tables; table; table = table->next) { |
1322 | add_sep(vbox, 3); |
1323 | build_assignment(vbox, frame, table); |
1324 | build_table(vbox, frame, table, wrap_width); |
1325 | } |
1326 | for (loop = frame->loops; loop; loop = loop->next) { |
1327 | add_sep(vbox, 3); |
1328 | build_loop(vbox, frame, loop); |
1329 | } |
1330 | return vbox; |
1331 | } |
1332 | |
1333 | |
1334 | /* ----- items ------------------------------------------------------------- */ |
1335 | |
1336 | |
1337 | static void set_item_color(struct inst *inst, const char *color) |
1338 | { |
1339 | GtkWidget *label; |
1340 | |
1341 | if (inst->vec) |
1342 | label = inst->vec->list_widget; |
1343 | else |
1344 | label = inst->obj->list_widget; |
1345 | if (label) |
1346 | label_in_box_bg(box_of_label(label), color); |
1347 | } |
1348 | |
1349 | |
1350 | void gui_frame_select_inst(struct inst *inst) |
1351 | { |
1352 | set_item_color(inst, COLOR_ITEM_SELECTED); |
1353 | } |
1354 | |
1355 | |
1356 | void gui_frame_deselect_inst(struct inst *inst) |
1357 | { |
1358 | set_item_color(inst, COLOR_ITEM_NORMAL); |
1359 | } |
1360 | |
1361 | |
1362 | static gboolean item_select_vec(GtkWidget *widget, GdkEventButton *event, |
1363 | gpointer data) |
1364 | { |
1365 | struct vec *vec = data; |
1366 | |
1367 | switch (event->button) { |
1368 | case 1: |
1369 | inst_select_vec(vec); |
1370 | redraw(); |
1371 | break; |
1372 | } |
1373 | return TRUE; |
1374 | } |
1375 | |
1376 | |
1377 | static gboolean item_select_obj(GtkWidget *widget, GdkEventButton *event, |
1378 | gpointer data) |
1379 | { |
1380 | struct obj *obj = data; |
1381 | |
1382 | switch (event->button) { |
1383 | case 1: |
1384 | inst_select_obj(obj); |
1385 | redraw(); |
1386 | break; |
1387 | } |
1388 | return TRUE; |
1389 | } |
1390 | |
1391 | |
1392 | static GtkWidget *item_label(GtkWidget *tab, char *s, int col, int row, |
1393 | gboolean (*cb)(GtkWidget *widget, GdkEventButton *event, gpointer data), |
1394 | gpointer data) |
1395 | { |
1396 | GtkWidget *label; |
1397 | |
1398 | label = label_in_box_new(s, "Click to select."); |
1399 | gtk_misc_set_padding(GTK_MISC(label), 0, 0); |
1400 | gtk_misc_set_alignment(GTK_MISC(label), 0, 0); |
1401 | gtk_widget_modify_font(label, item_list_font); |
1402 | gtk_table_attach_defaults(GTK_TABLE(tab), box_of_label(label), |
1403 | col, col+1, row, row+1); |
1404 | label_in_box_bg(box_of_label(label), COLOR_ITEM_NORMAL); |
1405 | |
1406 | if (cb) |
1407 | g_signal_connect(G_OBJECT(box_of_label(label)), |
1408 | "button_press_event", G_CALLBACK(cb), data); |
1409 | |
1410 | free(s); |
1411 | return label; |
1412 | } |
1413 | |
1414 | |
1415 | static GtkWidget *build_items(struct frame *frame) |
1416 | { |
1417 | GtkWidget *vbox, *hbox, *tab; |
1418 | struct order *order, *item; |
1419 | struct vec *vec; |
1420 | struct obj *obj; |
1421 | int n; |
1422 | char *s, *t; |
1423 | |
1424 | n = 0; |
1425 | for (vec = frame->vecs; vec; vec = vec->next) |
1426 | n++; |
1427 | for (obj = frame->objs; obj; obj = obj->next) |
1428 | if (obj->type != ot_meas) |
1429 | n++; |
1430 | |
1431 | vbox = gtk_vbox_new(FALSE, 0); |
1432 | add_sep(vbox, 3); |
1433 | |
1434 | hbox = gtk_hbox_new(FALSE, 0); |
1435 | gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); |
1436 | |
1437 | tab = gtk_table_new(n, 2, FALSE); |
1438 | gtk_box_pack_start(GTK_BOX(hbox), tab, FALSE, FALSE, 0); |
1439 | |
1440 | order = order_frame(frame); |
1441 | n = 0; |
1442 | for (item = order; item->vec || item->obj; item++) { |
1443 | if (item->obj) { |
1444 | s = print_obj(item->obj, item->vec); |
1445 | item->obj->list_widget = item_label(tab, s, 1, n, |
1446 | item_select_obj, item->obj); |
1447 | if (item->obj == instantiation_error) |
1448 | label_in_box_fg(item->obj->list_widget, |
1449 | COLOR_ITEM_ERROR); |
1450 | } else { |
1451 | t = stralloc_printf("%s: ", print_label(item->vec)); |
1452 | item_label(tab, t, 0, n, NULL, NULL); |
1453 | |
1454 | s = print_vec(item->vec); |
1455 | item->vec->list_widget = item_label(tab, s, 1, n, |
1456 | item_select_vec, item->vec); |
1457 | if (item->vec == instantiation_error) |
1458 | label_in_box_fg(item->vec->list_widget, |
1459 | COLOR_ITEM_ERROR); |
1460 | } |
1461 | n++; |
1462 | } |
1463 | free(order); |
1464 | |
1465 | return vbox; |
1466 | } |
1467 | |
1468 | |
1469 | static GtkWidget *build_meas(struct frame *frame) |
1470 | { |
1471 | GtkWidget *vbox, *hbox, *tab; |
1472 | struct obj *obj; |
1473 | int n; |
1474 | char *s; |
1475 | |
1476 | n = 0; |
1477 | for (obj = frame->objs; obj; obj = obj->next) |
1478 | if (obj->type == ot_meas) |
1479 | n++; |
1480 | |
1481 | vbox = gtk_vbox_new(FALSE, 0); |
1482 | add_sep(vbox, 3); |
1483 | |
1484 | hbox = gtk_hbox_new(FALSE, 0); |
1485 | gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); |
1486 | |
1487 | tab = gtk_table_new(n, 2, FALSE); |
1488 | gtk_box_pack_start(GTK_BOX(hbox), tab, FALSE, FALSE, 0); |
1489 | |
1490 | n = 0; |
1491 | for (obj = frame->objs; obj; obj = obj->next) { |
1492 | if (obj->type != ot_meas) |
1493 | continue; |
1494 | s = print_meas(obj); |
1495 | obj->list_widget = item_label(tab, s, 0, n, |
1496 | item_select_obj, obj); |
1497 | if (obj == instantiation_error) |
1498 | label_in_box_fg(obj->list_widget, COLOR_ITEM_ERROR); |
1499 | n++; |
1500 | } |
1501 | |
1502 | return vbox; |
1503 | } |
1504 | |
1505 | |
1506 | static void dont_build_items(struct frame *frame) |
1507 | { |
1508 | struct vec *vec; |
1509 | struct obj *obj; |
1510 | |
1511 | for (vec = frame->vecs; vec; vec = vec->next) |
1512 | vec->list_widget = NULL; |
1513 | for (obj = frame->objs; obj; obj = obj->next) |
1514 | obj->list_widget = NULL; |
1515 | } |
1516 | |
1517 | |
1518 | /* ----- package name ------------------------------------------------------ */ |
1519 | |
1520 | |
1521 | static int validate_pkg_name(const char *s, void *ctx) |
1522 | { |
1523 | if (!*s) |
1524 | return 0; |
1525 | while (*s) { |
1526 | if (*s < 32 || *s > 126) |
1527 | return 0; |
1528 | s++; |
1529 | } |
1530 | return 1; |
1531 | } |
1532 | |
1533 | static void unselect_pkg_name(void *data) |
1534 | { |
1535 | GtkWidget *widget = data; |
1536 | |
1537 | label_in_box_bg(widget, COLOR_PART_NAME); |
1538 | } |
1539 | |
1540 | |
1541 | static gboolean pkg_scroll_event(GtkWidget *widget, GdkEventScroll *event, |
1542 | gpointer data) |
1543 | { |
1544 | struct pkg *pkg, *last; |
1545 | |
1546 | switch (event->direction) { |
1547 | case GDK_SCROLL_DOWN: |
1548 | if (active_pkg->next) |
1549 | active_pkg = active_pkg->next; |
1550 | else |
1551 | active_pkg = pkgs->next; |
1552 | change_world(); |
1553 | break; |
1554 | case GDK_SCROLL_UP: |
1555 | last = NULL; |
1556 | for (pkg = pkgs->next; pkg && (!last || pkg != active_pkg); |
1557 | pkg = pkg->next) |
1558 | last = pkg; |
1559 | active_pkg = last; |
1560 | change_world(); |
1561 | break; |
1562 | default: |
1563 | /* ignore */; |
1564 | } |
1565 | return TRUE; |
1566 | } |
1567 | |
1568 | |
1569 | static gboolean pkg_name_edit_event(GtkWidget *widget, GdkEventButton *event, |
1570 | gpointer data) |
1571 | { |
1572 | switch (event->button) { |
1573 | case 1: |
1574 | inst_select_outside(widget, unselect_pkg_name); |
1575 | label_in_box_bg(widget, COLOR_PART_NAME_EDITING); |
1576 | status_set_type_entry(NULL, "package ="); |
1577 | status_set_name("Package name (actual)", "%s", pkg_name); |
1578 | edit_nothing(); |
1579 | edit_name(&pkg_name, validate_pkg_name, NULL, |
1580 | "Package name (template)"); |
1581 | break; |
1582 | } |
1583 | return TRUE; |
1584 | } |
1585 | |
1586 | |
1587 | static GtkWidget *build_pkg_name(void) |
1588 | { |
1589 | GtkWidget *label; |
1590 | |
1591 | label = label_in_box_new(pkg_name, |
1592 | "Package name. (Template) Click to edit."); |
1593 | gtk_misc_set_padding(GTK_MISC(label), 2, 2); |
1594 | gtk_misc_set_alignment(GTK_MISC(label), 0, 0); |
1595 | |
1596 | label_in_box_bg(label, COLOR_PART_NAME); |
1597 | |
1598 | g_signal_connect(G_OBJECT(box_of_label(label)), |
1599 | "button_press_event", G_CALLBACK(pkg_name_edit_event), NULL); |
1600 | g_signal_connect(G_OBJECT(box_of_label(label)), |
1601 | "scroll_event", G_CALLBACK(pkg_scroll_event), NULL); |
1602 | |
1603 | return box_of_label(label); |
1604 | } |
1605 | |
1606 | |
1607 | /* ----- packages ---------------------------------------------------------- */ |
1608 | |
1609 | |
1610 | static gboolean pkg_select_event(GtkWidget *widget, GdkEventButton *event, |
1611 | gpointer data) |
1612 | { |
1613 | struct pkg *pkg = data; |
1614 | |
1615 | switch (event->button) { |
1616 | case 1: |
1617 | active_pkg = pkg; |
1618 | /* @@@ we could actually skip instantiation here */ |
1619 | change_world(); |
1620 | break; |
1621 | } |
1622 | return TRUE; |
1623 | } |
1624 | |
1625 | |
1626 | static GtkWidget *build_pkg_names(void) |
1627 | { |
1628 | GtkWidget *hbox; |
1629 | struct pkg *pkg; |
1630 | GtkWidget *field; |
1631 | |
1632 | hbox = gtk_hbox_new(FALSE, 0); |
1633 | for (pkg = pkgs; pkg; pkg = pkg->next) |
1634 | if (pkg->name) { |
1635 | field = add_activator(hbox, pkg == active_pkg, |
1636 | pkg_select_event, pkg, |
1637 | "Package name. Click to make active.", |
1638 | "%s", pkg->name); |
1639 | g_signal_connect(G_OBJECT(box_of_label(field)), |
1640 | "scroll_event", |
1641 | G_CALLBACK(pkg_scroll_event), NULL); |
1642 | } |
1643 | return hbox; |
1644 | } |
1645 | |
1646 | |
1647 | /* ----- frame labels ------------------------------------------------------ */ |
1648 | |
1649 | |
1650 | static int validate_frame_name(const char *s, void *ctx) |
1651 | { |
1652 | struct frame *f; |
1653 | |
1654 | if (!is_id(s)) |
1655 | return 0; |
1656 | for (f = frames->next; f; f = f->next) |
1657 | if (!strcmp(f->name, s)) |
1658 | return 0; |
1659 | return 1; |
1660 | } |
1661 | |
1662 | |
1663 | static void unselect_frame(void *data) |
1664 | { |
1665 | struct frame *frame= data; |
1666 | |
1667 | /* |
1668 | * "unselect" means in this context that the selection has moved |
1669 | * elsewhere. However, this does not necessarily change the frame. |
1670 | * (And, in fact, since we rebuild the frame list anyway, the color |
1671 | * change here doesn't matter if selecting a different frame.) |
1672 | * So we revert from "editing" to "selected". |
1673 | */ |
1674 | label_in_box_bg(frame->label, COLOR_FRAME_SELECTED); |
1675 | } |
1676 | |
1677 | |
1678 | static void edit_frame(struct frame *frame) |
1679 | { |
1680 | const char *tip; |
1681 | |
1682 | inst_select_outside(frame, unselect_frame); |
1683 | label_in_box_bg(frame->label, COLOR_FRAME_EDITING); |
1684 | tip = "Frame name"; |
1685 | status_set_type_entry(NULL, "name ="); |
1686 | status_set_name(tip, "%s", frame->name); |
1687 | edit_nothing(); |
1688 | edit_unique(&frame->name, validate_frame_name, frame, tip); |
1689 | } |
1690 | |
1691 | |
1692 | void select_frame(struct frame *frame) |
1693 | { |
1694 | if (active_frame) |
1695 | label_in_box_bg(active_frame->label, COLOR_FRAME_UNSELECTED); |
1696 | active_frame = frame; |
1697 | change_world(); |
1698 | } |
1699 | |
1700 | |
1701 | static gboolean frame_press_event(GtkWidget *widget, GdkEventButton *event, |
1702 | gpointer data) |
1703 | { |
1704 | struct frame *frame = data; |
1705 | |
1706 | switch (event->button) { |
1707 | case 3: |
1708 | pop_up_frame(frame, event); |
1709 | return TRUE; |
1710 | } |
1711 | return FALSE; |
1712 | } |
1713 | |
1714 | |
1715 | static gboolean frame_release_event(GtkWidget *widget, GdkEventButton *event, |
1716 | gpointer data) |
1717 | { |
1718 | struct frame *frame = data; |
1719 | |
1720 | switch (event->button) { |
1721 | case 1: |
1722 | if (is_dragging(frame)) |
1723 | return FALSE; |
1724 | if (active_frame != frame) { |
1725 | select_frame(frame); |
1726 | } else { |
1727 | if (active_frame->name) { |
1728 | edit_nothing(); |
1729 | edit_frame(frame); |
1730 | } |
1731 | } |
1732 | return TRUE; |
1733 | } |
1734 | return FALSE; |
1735 | } |
1736 | |
1737 | |
1738 | static GtkWidget *build_frame_label(struct frame *frame) |
1739 | { |
1740 | GtkWidget *label; |
1741 | |
1742 | label = label_in_box_new(frame->name ? frame->name : "(root)", |
1743 | frame->name ? "Frame name. Click to select or edit." : |
1744 | "Root frame. Click to select."); |
1745 | gtk_misc_set_padding(GTK_MISC(label), 2, 2); |
1746 | gtk_misc_set_alignment(GTK_MISC(label), 0, 0); |
1747 | |
1748 | label_in_box_bg(label, active_frame == frame ? |
1749 | COLOR_FRAME_SELECTED : COLOR_FRAME_UNSELECTED); |
1750 | |
1751 | g_signal_connect(G_OBJECT(box_of_label(label)), |
1752 | "button_press_event", G_CALLBACK(frame_press_event), frame); |
1753 | g_signal_connect(G_OBJECT(box_of_label(label)), |
1754 | "button_release_event", G_CALLBACK(frame_release_event), frame); |
1755 | frame->label = label; |
1756 | |
1757 | if (frame != frames) |
1758 | setup_frame_drag(frame); |
1759 | |
1760 | return box_of_label(label); |
1761 | } |
1762 | |
1763 | |
1764 | /* ----- frame references -------------------------------------------------- */ |
1765 | |
1766 | |
1767 | static gboolean frame_ref_select_event(GtkWidget *widget, GdkEventButton *event, |
1768 | gpointer data) |
1769 | { |
1770 | struct obj *obj = data; |
1771 | |
1772 | switch (event->button) { |
1773 | case 1: |
1774 | obj->u.frame.ref->active_ref = data; |
1775 | change_world(); |
1776 | break; |
1777 | } |
1778 | return TRUE; |
1779 | } |
1780 | |
1781 | |
1782 | static GtkWidget *build_frame_refs(const struct frame *frame) |
1783 | { |
1784 | GtkWidget *hbox; |
1785 | struct obj *obj; |
1786 | char *tooltip; |
1787 | |
1788 | hbox = gtk_hbox_new(FALSE, 0); |
1789 | for (obj = frame->objs; obj; obj = obj->next) |
1790 | if (obj->type == ot_frame && |
1791 | obj->u.frame.ref == active_frame) { |
1792 | tooltip = stralloc_printf( |
1793 | "Frame <b>%s</b> is referenced here. " |
1794 | "Click to make active.", active_frame->name); |
1795 | add_activator(hbox, |
1796 | obj == obj->u.frame.ref->active_ref, |
1797 | frame_ref_select_event, obj, |
1798 | tooltip, |
1799 | "%d", obj->u.frame.lineno); |
1800 | free(tooltip); |
1801 | } |
1802 | return hbox; |
1803 | } |
1804 | |
1805 | |
1806 | /* ----- frames ------------------------------------------------------------ */ |
1807 | |
1808 | |
1809 | static void do_build_frames(GtkWidget *vbox, int wrap_width) |
1810 | { |
1811 | struct frame *frame; |
1812 | GtkWidget *hbox, *tab, *label, *packages, *refs, *vars, *items, *meas; |
1813 | int n = 0; |
1814 | int max_name_width, name_width; |
1815 | |
1816 | destroy_all_children(GTK_CONTAINER(vbox)); |
1817 | for (frame = frames; frame; frame = frame->next) |
1818 | n++; |
1819 | |
1820 | hbox = gtk_hbox_new(FALSE, 0); |
1821 | |
1822 | tab = gtk_table_new(n*2+3, 2, FALSE); |
1823 | gtk_table_set_row_spacings(GTK_TABLE(tab), 1); |
1824 | gtk_table_set_col_spacings(GTK_TABLE(tab), 1); |
1825 | |
1826 | gtk_box_pack_start(GTK_BOX(hbox), tab, FALSE, FALSE, 0); |
1827 | gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); |
1828 | |
1829 | label = build_pkg_name(); |
1830 | gtk_table_attach_defaults(GTK_TABLE(tab), label, 0, 1, 0, 1); |
1831 | max_name_width = get_widget_width(label); |
1832 | |
1833 | packages = build_pkg_names(); |
1834 | gtk_table_attach_defaults(GTK_TABLE(tab), packages, 1, 2, 0, 1); |
1835 | |
1836 | n = 0; |
1837 | for (frame = frames; frame; frame = frame->next) { |
1838 | label = build_frame_label(frame); |
1839 | gtk_table_attach_defaults(GTK_TABLE(tab), label, |
1840 | 0, 1, n*2+1, n*2+2); |
1841 | n++; |
1842 | name_width = get_widget_width(label); |
1843 | if (name_width > max_name_width) |
1844 | max_name_width = name_width; |
1845 | } |
1846 | |
1847 | wrap_width -= max_name_width+FRAME_AREA_MISC_WIDTH; |
1848 | n = 0; |
1849 | for (frame = frames; frame; frame = frame->next) { |
1850 | refs = build_frame_refs(frame); |
1851 | gtk_table_attach_defaults(GTK_TABLE(tab), refs, |
1852 | 1, 2, n*2+1, n*2+2); |
1853 | |
1854 | switch (sidebar) { |
1855 | case sidebar_var: |
1856 | vars = build_vars(frame, wrap_width); |
1857 | gtk_table_attach_defaults(GTK_TABLE(tab), vars, |
1858 | 1, 2, n*2+2, n*2+3); |
1859 | dont_build_items(frame); |
1860 | break; |
1861 | case sidebar_code: |
1862 | items = build_items(frame); |
1863 | gtk_table_attach_defaults(GTK_TABLE(tab), items, |
1864 | 1, 2, n*2+2, n*2+3); |
1865 | break; |
1866 | default: |
1867 | abort(); |
1868 | } |
1869 | |
1870 | n++; |
1871 | } |
1872 | |
1873 | if (sidebar == sidebar_code) { |
1874 | meas = build_meas(frames); |
1875 | gtk_table_attach_defaults(GTK_TABLE(tab), meas, |
1876 | 1, 2, n*2+2, n*2+3); |
1877 | } |
1878 | |
1879 | gtk_widget_show_all(hbox); |
1880 | } |
1881 | |
1882 | |
1883 | /* ----- packages ---------------------------------------------------------- */ |
1884 | |
1885 | |
1886 | static void build_packages(GtkWidget *vbox, int wrap_width) |
1887 | { |
1888 | GtkWidget *label, *hbox, *field; |
1889 | struct pkg *pkg; |
1890 | |
1891 | destroy_all_children(GTK_CONTAINER(vbox)); |
1892 | |
1893 | label = build_pkg_name(); |
1894 | gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0); |
1895 | |
1896 | for (pkg = pkgs; pkg; pkg = pkg->next) |
1897 | if (pkg->name) { |
1898 | hbox = gtk_hbox_new(FALSE, 0); |
1899 | field = add_activator(hbox, pkg == active_pkg, |
1900 | pkg_select_event, pkg, |
1901 | "Package name. Click to make active.", |
1902 | "%s", pkg->name); |
1903 | g_signal_connect(G_OBJECT(box_of_label(field)), |
1904 | "scroll_event", |
1905 | G_CALLBACK(pkg_scroll_event), NULL); |
1906 | gtk_box_pack_start(GTK_BOX(vbox), hbox, |
1907 | FALSE, FALSE, 2); |
1908 | } |
1909 | |
1910 | gtk_widget_show_all(vbox); |
1911 | } |
1912 | |
1913 | |
1914 | /* ----- sidebar dispatcher ------------------------------------------------ */ |
1915 | |
1916 | |
1917 | void build_frames(GtkWidget *vbox, int wrap_width) |
1918 | { |
1919 | if (sidebar == sidebar_pkg) |
1920 | build_packages(vbox, wrap_width); |
1921 | else |
1922 | do_build_frames(vbox, wrap_width); |
1923 | } |
1924 |
Branches:
master