1
0
mirror of git://projects.qi-hardware.com/fped.git synced 2024-11-17 23:11:53 +02:00
fped/gui_frame.c
Werner Almesberger 4c3dea7111 support switching variables between assignment and key (WIP)
This is a little awkward: to change a variable used as key to an
assignment, one first had to change the name such that it doesn't
clash, hit Enter, and then edit the variable again to change its
type.

Variable type changes should pick up the edit in progress and allow
a type change to also imply acceptance of the variable.
2012-05-28 02:54:50 -03:00

1876 lines
43 KiB
C

/*
* gui_frame.c - GUI, frame window
*
* Written 2009, 2010, 2012 by Werner Almesberger
* Copyright 2009, 2010, 2012 by Werner Almesberger
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <string.h>
#include <gtk/gtk.h>
#include "util.h"
#include "error.h"
#include "dump.h"
#include "inst.h"
#include "obj.h"
#include "delete.h"
#include "unparse.h"
#include "gui_util.h"
#include "gui_style.h"
#include "gui_status.h"
#include "gui_tool.h"
#include "gui_canvas.h"
#include "gui.h"
#include "gui_frame_drag.h"
#include "gui_frame.h"
int show_vars = 1;
/* ----- add elements, shared ---------------------------------------------- */
/* @@@ merge with fpd.y */
static void add_table(struct frame *frame, struct table **anchor)
{
struct table *table, **walk;
table = zalloc_type(struct table);
table->vars = zalloc_type(struct var);
table->vars->name = unique("_");
table->vars->frame = frame;
table->vars->table = table;
table->rows = zalloc_type(struct row);
table->rows->table = table;
table->rows->values = zalloc_type(struct value);
table->rows->values->expr = parse_expr("0");
table->rows->values->row = table->rows;
table->active_row = table->rows;
if (anchor) {
table->next = *anchor;
*anchor = table;
} else {
for (walk = &frame->tables; *walk; walk = &(*walk)->next);
*walk = table;
}
change_world();
}
static void add_loop(struct frame *frame, struct loop **anchor)
{
struct loop *loop, **walk;
loop = zalloc_type(struct loop);
loop->var.name = unique("_");
loop->var.frame = frame;
loop->from.expr = parse_expr("0");
loop->to.expr = parse_expr("0");
if (anchor) {
loop->next = *anchor;
*anchor = loop;
} else {
loop->next = NULL;
for (walk = &frame->loops; *walk; walk = &(*walk)->next);
*walk = loop;
}
change_world();
}
/* ----- popup dispatcher -------------------------------------------------- */
static void *popup_data;
static void pop_up(GtkWidget *menu, GdkEventButton *event, void *data)
{
popup_data = data;
gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
event->button, event->time);
}
/* ----- popup: frame ------------------------------------------------------ */
static GtkItemFactory *factory_frame;
static GtkWidget *popup_frame_widget;
static void popup_add_frame(void)
{
struct frame *parent = popup_data;
struct frame *new;
new = zalloc_type(struct frame);
new->name = unique("_");
new->next = parent->next;
parent->next = new;
change_world();
}
static void popup_del_frame(void)
{
struct frame *frame = popup_data;
assert(frame != frames);
delete_frame(frame);
if (active_frame == frame)
select_frame(frames);
change_world();
}
static void popup_add_table(void)
{
add_table(popup_data, NULL);
}
static void popup_add_loop(void)
{
add_loop(popup_data, NULL);
}
static GtkItemFactoryEntry popup_frame_entries[] = {
{ "/Add frame", NULL, popup_add_frame,0, "<Item>" },
{ "/sep0", NULL, NULL, 0, "<Separator>" },
{ "/Add variable", NULL, popup_add_table,0, "<Item>" },
{ "/Add loop", NULL, popup_add_loop, 0, "<Item>" },
{ "/sep1", NULL, NULL, 0, "<Separator>" },
{ "/Delete frame", NULL, popup_del_frame,0, "<Item>" },
{ "/sep2", NULL, NULL, 0, "<Separator>" },
{ "/Close", NULL, NULL, 0, "<Item>" },
{ NULL }
};
static gboolean can_add_frame(void)
{
const struct frame *frame;
for (frame = frames->next; frame; frame = frame->next)
if (!strcmp(frame->name, "_"))
return FALSE;
return TRUE;
}
static gboolean can_add_var(const struct frame *frame)
{
const struct table *table;
const struct var *var;
const struct loop *loop;
for (table = frame->tables; table; table = table->next)
for (var = table->vars; var; var = var->next)
if (!strcmp(var->name, "_"))
return FALSE;
for (loop = frame->loops; loop; loop = loop->next)
if (!strcmp(loop->var.name, "_"))
return FALSE;
return TRUE;
}
static void enable_add_var(struct frame *frame, GtkItemFactory *factory)
{
gboolean add_var;
add_var = can_add_var(frame);
gtk_widget_set_sensitive(
gtk_item_factory_get_item(factory, "/Add variable"), add_var);
gtk_widget_set_sensitive(
gtk_item_factory_get_item(factory, "/Add loop"), add_var);
}
static void pop_up_frame(struct frame *frame, GdkEventButton *event)
{
gtk_widget_set_sensitive(
gtk_item_factory_get_item(factory_frame, "/Delete frame"),
frame != frames);
gtk_widget_set_sensitive(
gtk_item_factory_get_item(factory_frame, "/Add frame"),
can_add_frame());
enable_add_var(frame, factory_frame);
pop_up(popup_frame_widget, event, frame);
}
/* ----- popup: single variable -------------------------------------------- */
static GtkItemFactory *factory_single_var;
static GtkWidget *popup_single_var_widget;
static void add_row_here(struct table *table, struct row **anchor)
{
struct row *row;
const struct value *walk;
struct value *value;
row = zalloc_type(struct row);
row->table = table;
/* @@@ future: adjust type */
for (walk = table->rows->values; walk; walk = walk->next) {
value = zalloc_type(struct value);
value->expr = parse_expr("0");
value->row = row;
value->next = row->values;
row->values = value;
}
row->next = *anchor;
*anchor = row;
change_world();
}
static void add_column_here(struct table *table, struct var **anchor)
{
const struct var *walk;
struct var *var;
struct row *row;
struct value *value;
struct value **value_anchor;
int n = 0, i;
for (walk = table->vars; walk != *anchor; walk = walk->next)
n++;
var = zalloc_type(struct var);
var->name = unique("_");
var->frame = table->vars->frame;
var->table = table;
var->next = *anchor;
*anchor = var;
for (row = table->rows; row; row = row->next) {
value_anchor = &row->values;
for (i = 0; i != n; i++)
value_anchor = &(*value_anchor)->next;
value = zalloc_type(struct value);
value->expr = parse_expr("0");
value->row = row;
value->next = *value_anchor;
*value_anchor = value;
}
change_world();
}
static void popup_add_row(void)
{
struct var *var = popup_data;
add_row_here(var->table, &var->table->rows);
}
static void popup_add_column(void)
{
struct var *var = popup_data;
add_column_here(var->table, &var->next);
}
static void popup_del_table(void)
{
struct var *var = popup_data;
delete_table(var->table);
change_world();
}
static void popup_add_table_from_var(void)
{
struct var *var = popup_data;
add_table(var->frame, &var->table->next);
}
static void popup_add_loop_from_var(void)
{
struct var *var = popup_data;
add_loop(var->frame, NULL);
}
static GtkItemFactoryEntry popup_single_var_entries[] = {
{ "/Add row", NULL, popup_add_row, 0, "<Item>" },
{ "/Add column", NULL, popup_add_column, 0, "<Item>" },
{ "/sep1", NULL, NULL, 0, "<Separator>" },
{ "/Delete variable", NULL, popup_del_table,0, "<Item>" },
{ "/sep2", NULL, NULL, 0, "<Separator>" },
{ "/Add variable", NULL, popup_add_table_from_var,
0, "<Item>" },
{ "/Add loop", NULL, popup_add_loop_from_var,
0, "<Item>" },
{ "/sep3", NULL, NULL, 0, "<Separator>" },
{ "/Close", NULL, NULL, 0, "<Item>" },
{ NULL }
};
static void pop_up_single_var(struct var *var, GdkEventButton *event)
{
gtk_widget_set_sensitive(
gtk_item_factory_get_item(factory_single_var, "/Add column"),
can_add_var(var->frame));
enable_add_var(var->frame, factory_single_var);
pop_up(popup_single_var_widget, event, var);
}
/* ----- popup: table variable --------------------------------------------- */
static GtkItemFactory *factory_table_var;
static GtkWidget *popup_table_var_widget;
static void popup_del_column(void)
{
struct var *var = popup_data;
const struct var *walk;
int n = 0;
for (walk = var->table->vars; walk != var; walk = walk->next)
n++;
delete_column(var->table, n);
change_world();
}
static GtkItemFactoryEntry popup_table_var_entries[] = {
{ "/Add row", NULL, popup_add_row, 0, "<Item>" },
{ "/Add column", NULL, popup_add_column, 0, "<Item>" },
{ "/sep1", NULL, NULL, 0, "<Separator>" },
{ "/Delete table", NULL, popup_del_table,0, "<Item>" },
{ "/Delete column", NULL, popup_del_column, 0, "<Item>" },
{ "/sep2", NULL, NULL, 0, "<Separator>" },
{ "/Add variable", NULL, popup_add_table_from_var,
0, "<Item>" },
{ "/Add loop", NULL, popup_add_loop_from_var,
0, "<Item>" },
{ "/sep3", NULL, NULL, 0, "<Separator>" },
{ "/Close", NULL, NULL, 0, "<Item>" },
{ NULL }
};
static void pop_up_table_var(struct var *var, GdkEventButton *event)
{
gtk_widget_set_sensitive(
gtk_item_factory_get_item(factory_table_var, "/Delete column"),
var->table->vars->next != NULL);
gtk_widget_set_sensitive(
gtk_item_factory_get_item(factory_table_var, "/Add column"),
can_add_var(var->frame));
enable_add_var(var->frame, factory_table_var);
pop_up(popup_table_var_widget, event, var);
}
/* ----- popup: table value ------------------------------------------------ */
static GtkItemFactory *factory_table_value;
static GtkWidget *popup_table_value_widget;
static void popup_add_column_by_value(void)
{
struct value *value = popup_data;
const struct value *walk;
struct table *table = value->row->table;
struct var *var = table->vars;
for (walk = value->row->values; walk != value; walk = walk->next)
var = var->next;
add_column_here(table, &var->next);
}
static void popup_add_row_by_value(void)
{
struct value *value = popup_data;
add_row_here(value->row->table, &value->row->next);
}
static void popup_del_row(void)
{
struct value *value = popup_data;
struct table *table = value->row->table;
delete_row(value->row);
if (table->active_row == value->row)
table->active_row = table->rows;
change_world();
}
static void popup_del_column_by_value(void)
{
struct value *value = popup_data;
const struct value *walk;
int n = 0;
for (walk = value->row->values; walk != value; walk = walk->next)
n++;
delete_column(value->row->table, n);
change_world();
}
static GtkItemFactoryEntry popup_table_value_entries[] = {
{ "/Add row", NULL, popup_add_row_by_value, 0, "<Item>" },
{ "/Add column", NULL, popup_add_column_by_value,
0, "<Item>" },
{ "/sep1", NULL, NULL, 0, "<Separator>" },
{ "/Delete row", NULL, popup_del_row, 0, "<Item>" },
{ "/Delete column", NULL, popup_del_column_by_value,
0, "<Item>" },
{ "/sep2", NULL, NULL, 0, "<Separator>" },
{ "/Close", NULL, NULL, 0, "<Item>" },
{ NULL }
};
static void pop_up_table_value(struct value *value, GdkEventButton *event)
{
gtk_widget_set_sensitive(
gtk_item_factory_get_item(factory_table_value, "/Delete row"),
value->row->table->rows->next != NULL);
gtk_widget_set_sensitive(
gtk_item_factory_get_item(factory_table_value, "/Delete column"),
value->row->table->vars->next != NULL);
pop_up(popup_table_value_widget, event, value);
}
/* ----- popup: loop ------------------------------------------------------- */
static GtkItemFactory *factory_loop_var;
static GtkWidget *popup_loop_var_widget;
static void popup_del_loop(void)
{
struct loop *loop = popup_data;
delete_loop(loop);
change_world();
}
static void popup_add_table_from_loop(void)
{
struct loop *loop = popup_data;
add_table(loop->var.frame, NULL);
}
static void popup_add_loop_from_loop(void)
{
struct loop *loop = popup_data;
add_loop(loop->var.frame, &loop->next);
}
static GtkItemFactoryEntry popup_loop_var_entries[] = {
{ "/Delete loop", NULL, popup_del_loop, 0, "<Item>" },
{ "/sep1", NULL, NULL, 0, "<Separator>" },
{ "/Add variable", NULL, popup_add_table_from_loop,
0, "<Item>" },
{ "/Add loop", NULL, popup_add_loop_from_loop,
0, "<Item>" },
{ "/sep2", NULL, NULL, 0, "<Separator>" },
{ "/Close", NULL, NULL, 0, "<Item>" },
{ NULL }
};
static void pop_up_loop_var(struct loop *loop, GdkEventButton *event)
{
enable_add_var(loop->var.frame, factory_loop_var);
pop_up(popup_loop_var_widget, event, loop);
}
/* ----- make popups ------------------------------------------------------- */
static GtkWidget *make_popup(const char *name, GtkItemFactory **factory,
GtkItemFactoryEntry *entries)
{
GtkWidget *popup;
int n;
n = 0;
for (n = 0; entries[n].path; n++);
*factory = gtk_item_factory_new(GTK_TYPE_MENU, name, NULL);
gtk_item_factory_create_items(*factory, n, entries, NULL);
popup = gtk_item_factory_get_widget(*factory, name);
return popup;
}
void make_popups(void)
{
popup_frame_widget = make_popup("<FpedFramePopUp>",
&factory_frame, popup_frame_entries);
popup_single_var_widget = make_popup("<FpedSingleVarPopUp>",
&factory_single_var, popup_single_var_entries);
popup_table_var_widget = make_popup("<FpedTableVarPopUp>",
&factory_table_var, popup_table_var_entries);
popup_table_value_widget = make_popup("<FpedTableValusPopUp>",
&factory_table_value, popup_table_value_entries);
popup_loop_var_widget = make_popup("<FpedLoopVarPopUp>",
&factory_loop_var, popup_loop_var_entries);
}
/* ----- variable list ----------------------------------------------------- */
static void add_sep(GtkWidget *box, int size)
{
GtkWidget *sep;
GdkColor black = { 0, 0, 0, 0 };
sep = gtk_drawing_area_new();
gtk_box_pack_start(GTK_BOX(box), sep, FALSE, TRUE, size);
gtk_widget_modify_bg(sep, GTK_STATE_NORMAL, &black);
}
/* ----- variable name editor ---------------------------------------------- */
int find_var_in_frame(const struct frame *frame, const char *name,
const struct var *self)
{
const struct table *table;
const struct loop *loop;
const struct var *var;
for (table = frame->tables; table; table = table->next)
for (var = table->vars; var; var = var->next)
if (var != self && !var->key &&
!strcmp(var->name, name))
return 1;
for (loop = frame->loops; loop; loop = loop->next)
if (&loop->var != self && !strcmp(loop->var.name, name))
return 1;
return 0;
}
static int validate_var_name(const char *s, void *ctx)
{
struct var *var = ctx;
if (!is_id(s))
return 0;
if (var->key)
return 1;
return !find_var_in_frame(var->frame, s, var);
}
static void unselect_var(void *data)
{
struct var *var = data;
label_in_box_bg(var->widget, COLOR_VAR_PASSIVE);
}
static void show_value(const struct expr *expr, const struct frame *frame)
{
const char *value_string;
struct num value;
status_set_type_x(NULL, "value =");
value_string = eval_str(expr, frame);
if (value_string) {
status_set_x(NULL, "\"%s\"", value_string);
} else {
value = eval_num(expr, frame);
if (is_undef(value))
status_set_x(NULL, "undefined");
else
status_set_x(NULL, "%lg%s", value.n, str_unit(value));
}
}
static void show_var_value(const struct var *var, const struct frame *frame)
{
const struct var *walk;
const struct value *value;
if (!var->table)
return;
value = var->table->active_row->values;
for (walk = var->table->vars; walk != var; walk = walk->next)
value = value->next;
show_value(value->expr, frame);
}
static void edit_var(struct var *var,
void (*set_values)(void *user, const struct value *values, int n_values),
void *user, int max_values)
{
inst_select_outside(var, unselect_var);
label_in_box_bg(var->widget, COLOR_VAR_EDITING);
status_set_type_entry(NULL, "name =");
status_set_name("Variable name", "%s", var->name);
show_var_value(var, var->frame);
edit_nothing();
edit_var_type(var);
edit_unique_with_values(&var->name, validate_var_name, var,
set_values, user, max_values,
"Variable name. "
"Shortcut:<b><i>name</i>=<i>value</i>,<i>...</i> </b>");
}
static void set_col_values(void *user, const struct value *values,
int n_values);
void reselect_var(struct var *var)
{
edit_var(var, set_col_values, var, -1);
}
/* ----- value editor ------------------------------------------------------ */
static void unselect_value(void *data)
{
struct value *value = data;
/*
* This condition is a little cryptic. Here is what it does:
*
* IF table/assignment (not loop)
* AND the current row is the active (selected) row
* AND it's an assignment (not a table).
*
* We need the last condition because the expressions of assignments
* are drawn with COLOR_EXPR_PASSIVE. (See build_assignment.)
*/
label_in_box_bg(value->widget,
value->row && value->row->table->active_row == value->row &&
(value->row->table->rows->next || value->row->table->vars->next) ?
COLOR_CHOICE_SELECTED : COLOR_EXPR_PASSIVE);
}
static void edit_value(struct value *value, const struct frame *frame)
{
inst_select_outside(value, unselect_value);
label_in_box_bg(value->widget, COLOR_EXPR_EDITING);
show_value(value->expr, frame);
edit_nothing();
edit_expr(&value->expr, "Value");
}
static void edit_value_list(struct value *value, const struct frame *frame,
void (*set_values)(void *user, const struct value *values, int n_values),
void *user)
{
inst_select_outside(value, unselect_value);
label_in_box_bg(value->widget, COLOR_EXPR_EDITING);
show_value(value->expr, frame);
edit_nothing();
edit_expr_list(value->expr, set_values, user, "Value(s)");
}
/* ----- activator --------------------------------------------------------- */
static GtkWidget *add_activator(GtkWidget *hbox, int active,
gboolean (*cb)(GtkWidget *widget, GdkEventButton *event, gpointer data),
gpointer user, const char *tooltip, const char *fmt, ...)
{
GtkWidget *label;
va_list ap;
char buf[100];
va_start(ap, fmt);
vsprintf(buf, fmt, ap);
va_end(ap);
label = label_in_box_new(buf, tooltip);
gtk_misc_set_padding(GTK_MISC(label), 2, 2);
gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
label_in_box_bg(label,
active ? COLOR_CHOICE_SELECTED : COLOR_CHOICE_UNSELECTED);
gtk_box_pack_start(GTK_BOX(hbox), box_of_label(label),
FALSE, FALSE, 2);
g_signal_connect(G_OBJECT(box_of_label(label)),
"button_press_event", G_CALLBACK(cb), user);
return label;
}
/* ----- assignments ------------------------------------------------------- */
static void set_col_values(void *user, const struct value *values,
int n_values)
{
struct var *var = user;
struct table *table = var->table;
struct value *value;
const struct var *walk;
struct row **row;
row = &table->rows;
while (values) {
if (!*row)
add_row_here(table, row);
value = (*row)->values;
for (walk = table->vars; walk != var; walk = walk->next)
value = value->next;
free_expr(value->expr);
value->expr = values->expr;
values = values->next;
row = &(*row)->next;
}
}
static gboolean assignment_var_select_event(GtkWidget *widget,
GdkEventButton *event, gpointer data)
{
struct var *var = data;
switch (event->button) {
case 1:
edit_var(var, set_col_values, var, -1);
break;
case 3:
pop_up_single_var(var, event);
break;
}
return TRUE;
}
static gboolean assignment_value_select_event(GtkWidget *widget,
GdkEventButton *event, gpointer data)
{
struct value *value = data;
switch (event->button) {
case 1:
edit_nothing();
edit_value(value, value->row->table->vars->frame);
break;
}
return TRUE;
}
/*
* In tables, expressions in the active row have a COLOR_CHOICE_SELECTED
* background. While expressions in assignments are technically on the active
* (and only) row, we use COLOR_VAR_PASSIVE for better readability.
*/
static void build_assignment(GtkWidget *vbox, struct frame *frame,
struct table *table)
{
GtkWidget *hbox, *field;
char *name, *expr;
if (!table->vars || table->vars->next)
return;
if (!table->rows || table->rows->next)
return;
hbox = gtk_hbox_new(FALSE, 0);
gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
name = stralloc_printf("%s%s", table->vars->key ? "?" : "",
table->vars->name);
field = label_in_box_new(name, "Variable name. Click to edit.");
free(name);
gtk_box_pack_start(GTK_BOX(hbox), box_of_label(field), FALSE, FALSE, 0);
label_in_box_bg(field, COLOR_VAR_PASSIVE);
table->vars->widget = field;
g_signal_connect(G_OBJECT(box_of_label(field)),
"button_press_event",
G_CALLBACK(assignment_var_select_event), table->vars);
gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new(" = "),
FALSE, FALSE, 0);
expr = unparse(table->rows->values->expr);
field = label_in_box_new(expr, "Variable value. Click to edit.");
free(expr);
gtk_box_pack_start(GTK_BOX(hbox), box_of_label(field), FALSE, FALSE, 0);
label_in_box_bg(field, COLOR_EXPR_PASSIVE);
table->rows->values->widget = field;
g_signal_connect(G_OBJECT(box_of_label(field)),
"button_press_event",
G_CALLBACK(assignment_value_select_event), table->rows->values);
}
/* ----- tables ------------------------------------------------------------ */
static void select_row(struct row *row)
{
struct table *table = row->table;
struct value *value;
for (value = table->active_row->values; value; value = value->next)
label_in_box_bg(value->widget, COLOR_ROW_UNSELECTED);
table->active_row = row;
for (value = table->active_row->values; value; value = value->next)
label_in_box_bg(value->widget, COLOR_ROW_SELECTED);
}
static void set_row_values(void *user, const struct value *values,
int n_values)
{
struct value *value = user;
struct row *row = value->row;
struct table *table = row->table;
struct var **var;
const struct value *walk;
int first = 1;
var = &table->vars;
for (walk = row->values; walk != value; walk = walk->next)
var = &(*var)->next;
while (values) {
if (!*var)
add_column_here(table, var);
if (first)
first = 0;
else
value = value->next;
free_expr(value->expr);
value->expr = values->expr;
values = values->next;
var = &(*var)->next;
}
}
static gboolean table_var_press_event(GtkWidget *widget,
GdkEventButton *event, gpointer data)
{
struct var *var = data;
switch (event->button) {
case 3:
pop_up_table_var(var, event);
return TRUE;
}
return FALSE;
}
static gboolean table_var_release_event(GtkWidget *widget,
GdkEventButton *event, gpointer data)
{
struct var *var = data;
switch (event->button) {
case 1:
if (is_dragging(var))
return FALSE;
edit_var(var, set_col_values, var, -1);
return TRUE;
}
return FALSE;
}
static gboolean table_value_press_event(GtkWidget *widget,
GdkEventButton *event, gpointer data)
{
struct value *value = data;
switch (event->button) {
case 3:
pop_up_table_value(value, event);
return TRUE;
}
return FALSE;
}
static gboolean table_value_release_event(GtkWidget *widget,
GdkEventButton *event, gpointer data)
{
struct value *value = data;
switch (event->button) {
case 1:
if (is_dragging(value))
return FALSE;
if (!value->row ||
value->row->table->active_row == value->row) {
edit_nothing();
edit_value_list(value, value->row->table->vars->frame,
set_row_values, value);
} else {
select_row(value->row);
change_world();
}
return TRUE;
}
return FALSE;
}
static gboolean table_scroll_event(GtkWidget *widget, GdkEventScroll *event,
gpointer data)
{
struct table *table = data;
struct row *row, *last;
switch (event->direction) {
case GDK_SCROLL_UP:
last = NULL;
for (row = table->rows;
row && (!last || row != table->active_row); row = row->next)
last = row;
table->active_row = last;
change_world();
break;
case GDK_SCROLL_DOWN:
table->active_row = table->active_row->next;
if (!table->active_row)
table->active_row = table->rows;
change_world();
break;
default:
/* ignore */;
}
return TRUE;
}
/* @@@ this function is too long */
static void build_table(GtkWidget *vbox, struct frame *frame,
struct table *table, int wrap_width)
{
GtkWidget *tab, *field;
GtkWidget *evbox, *align, *sep;
struct var *var;
struct row *row;
struct value *value;
int n_vars = 0, n_rows = 0;
int n_var, n_row, pos;
char *name, *expr;
GdkColor color;
for (var = table->vars; var; var = var->next)
n_vars++;
for (row = table->rows; row; row = row->next)
n_rows++;
if (n_vars == 1 && n_rows == 1)
return;
var = table->vars;
n_var = 0;
n_vars = 0;
while (var) {
if (n_vars) {
gtk_table_resize(GTK_TABLE(tab), n_rows, n_vars+1);
} else {
evbox = gtk_event_box_new();
align = gtk_alignment_new(0, 0, 0, 0);
gtk_container_add(GTK_CONTAINER(align), evbox);
gtk_box_pack_start(GTK_BOX(vbox), align,
FALSE, FALSE, 0);
tab = gtk_table_new(n_rows+1, n_vars, FALSE);
gtk_container_add(GTK_CONTAINER(evbox), tab);
color = get_color(COLOR_VAR_TABLE_SEP);
gtk_widget_modify_bg(GTK_WIDGET(evbox),
GTK_STATE_NORMAL, &color);
gtk_table_set_row_spacings(GTK_TABLE(tab), 1);
gtk_table_set_col_spacings(GTK_TABLE(tab), 1);
}
name = stralloc_printf("%s%s", var->key ? "?" : "", var->name);
field = label_in_box_new(name,
"Variable (column) name. Click to edit.");
free(name);
gtk_table_attach_defaults(GTK_TABLE(tab), box_of_label(field),
n_vars, n_vars+1, 0, 1);
label_in_box_bg(field, COLOR_VAR_PASSIVE);
g_signal_connect(G_OBJECT(box_of_label(field)),
"button_press_event",
G_CALLBACK(table_var_press_event), var);
g_signal_connect(G_OBJECT(box_of_label(field)),
"button_release_event",
G_CALLBACK(table_var_release_event), var);
g_signal_connect(G_OBJECT(box_of_label(field)),
"scroll_event",
G_CALLBACK(table_scroll_event), table);
var->widget = field;
setup_var_drag(var);
n_row = 0;
for (row = table->rows; row; row = row->next) {
value = row->values;
for (pos = 0; pos != n_var; pos++)
value = value->next;
expr = unparse(value->expr);
field = label_in_box_new(expr,
"Variable value. Click to select row or to edit.");
free(expr);
gtk_table_attach_defaults(GTK_TABLE(tab),
box_of_label(field),
n_vars, n_vars+1,
n_row+1, n_row+2);
label_in_box_bg(field, table->active_row == row ?
COLOR_ROW_SELECTED : COLOR_ROW_UNSELECTED);
g_signal_connect(G_OBJECT(box_of_label(field)),
"button_press_event",
G_CALLBACK(table_value_press_event), value);
g_signal_connect(G_OBJECT(box_of_label(field)),
"button_release_event",
G_CALLBACK(table_value_release_event), value);
g_signal_connect(G_OBJECT(box_of_label(field)),
"scroll_event",
G_CALLBACK(table_scroll_event), table);
value->widget = field;
setup_value_drag(value);
n_row++;
}
/*
* Wrap tables wider than the screen area available for
* variables and tables. Don't wrap before having output at
* least one column.
*/
if (n_vars && get_widget_width(tab) > wrap_width) {
/*
* Resizing alone doesn't hide extra columns. We have
* to explicitly remove their content as well.
*/
gtk_container_remove(GTK_CONTAINER(tab),
box_of_label(var->widget));
for (row = table->rows; row; row = row->next) {
value = row->values;
for (pos = 0; pos != n_var; pos++)
value = value->next;
gtk_container_remove(GTK_CONTAINER(tab),
box_of_label(value->widget));
}
gtk_table_resize(GTK_TABLE(tab), n_rows, n_vars);
sep = gtk_vbox_new(FALSE, 0);
gtk_box_pack_start(GTK_BOX(vbox), sep,
FALSE, FALSE, 1);
n_vars = 0;
continue;
}
var = var->next;
n_var++;
n_vars++;
}
}
/* ----- loops ------------------------------------------------------------- */
static void set_loop_values(void *user, const struct value *values,
int n_values)
{
struct loop *loop = user;
switch (n_values) {
case 2:
if (loop->to.expr)
free_expr(loop->to.expr);
loop->to.expr = values->next->expr;
/* fall through */
case 1:
if (loop->from.expr)
free_expr(loop->from.expr);
loop->from.expr = values->expr;
break;
case 0:
break;
default:
abort();
}
}
static gboolean loop_var_select_event(GtkWidget *widget,
GdkEventButton *event, gpointer data)
{
struct loop *loop = data;
switch (event->button) {
case 1:
edit_var(&loop->var, set_loop_values, loop, 2);
break;
case 3:
pop_up_loop_var(loop, event);
break;
}
return TRUE;
}
static gboolean loop_from_select_event(GtkWidget *widget,
GdkEventButton *event, gpointer data)
{
struct loop *loop = data;
switch (event->button) {
case 1:
edit_nothing();
edit_value(&loop->from, loop->var.frame);
break;
}
return TRUE;
}
static gboolean loop_to_select_event(GtkWidget *widget,
GdkEventButton *event, gpointer data)
{
struct loop *loop = data;
switch (event->button) {
case 1:
edit_nothing();
edit_value(&loop->to, loop->var.frame);
break;
}
return TRUE;
}
static gboolean loop_select_event(GtkWidget *widget, GdkEventButton *event,
gpointer data)
{
struct loop *loop = data;
switch (event->button) {
case 1:
loop->active =
(long) gtk_object_get_data(GTK_OBJECT(widget), "value");
change_world();
break;
}
return TRUE;
}
static gboolean loop_scroll_event(GtkWidget *widget, GdkEventScroll *event,
gpointer data)
{
struct loop *loop = data;
switch (event->direction) {
case GDK_SCROLL_UP:
if (loop->active < loop->iterations-1) {
loop->active++;
change_world();
}
break;
case GDK_SCROLL_DOWN:
if (loop->active) {
loop->active--;
change_world();
}
break;
default:
/* ignore */;
}
return TRUE;
}
static void build_loop(GtkWidget *vbox, struct frame *frame,
struct loop *loop)
{
GtkWidget *hbox, *field, *label;
char *expr;
int i;
hbox = gtk_hbox_new(FALSE, 0);
gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
field = label_in_box_new(loop->var.name,
"Variable name. Click to edit.");
gtk_box_pack_start(GTK_BOX(hbox), box_of_label(field), FALSE, FALSE, 0);
label_in_box_bg(field, COLOR_VAR_PASSIVE);
if (instantiation_error == loop)
label_in_box_fg(field, COLOR_ITEM_ERROR);
g_signal_connect(G_OBJECT(box_of_label(field)),
"button_press_event",
G_CALLBACK(loop_var_select_event), loop);
loop->var.widget = field;
gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new(" = "),
FALSE, FALSE, 0);
expr = unparse(loop->from.expr);
field = label_in_box_new(expr,
"Start value of loop. Click to edit.");
free(expr);
gtk_box_pack_start(GTK_BOX(hbox), box_of_label(field), FALSE, FALSE, 0);
label_in_box_bg(field, COLOR_EXPR_PASSIVE);
g_signal_connect(G_OBJECT(box_of_label(field)),
"button_press_event",
G_CALLBACK(loop_from_select_event), loop);
loop->from.widget = field;
gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new(" ... "),
FALSE, FALSE, 0);
expr = unparse(loop->to.expr);
field = label_in_box_new(expr, "End value of loop. Click to edit.");
free(expr);
gtk_box_pack_start(GTK_BOX(hbox), box_of_label(field), FALSE, FALSE, 0);
label_in_box_bg(field, COLOR_EXPR_PASSIVE);
g_signal_connect(G_OBJECT(box_of_label(field)),
"button_press_event",
G_CALLBACK(loop_to_select_event), loop);
loop->to.widget = field;
gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new(" ("),
FALSE, FALSE, 0);
for (i = 0; i != loop->iterations; i++) {
label = add_activator(hbox, loop->active == i,
loop_select_event, loop,
"Loop value. Click to make active.",
"%g", loop->n+i);
gtk_object_set_data(GTK_OBJECT(box_of_label(label)), "value",
(gpointer) (long) i);
g_signal_connect(G_OBJECT(box_of_label(label)),
"scroll_event",
G_CALLBACK(loop_scroll_event), loop);
}
gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new(")"),
FALSE, FALSE, 0);
}
/* ----- the list of variables, tables, and loops -------------------------- */
static GtkWidget *build_vars(struct frame *frame, int wrap_width)
{
GtkWidget *vbox;
struct table *table;
struct loop *loop;
vbox = gtk_vbox_new(FALSE, 0);
for (table = frame->tables; table; table = table->next) {
add_sep(vbox, 3);
build_assignment(vbox, frame, table);
build_table(vbox, frame, table, wrap_width);
}
for (loop = frame->loops; loop; loop = loop->next) {
add_sep(vbox, 3);
build_loop(vbox, frame, loop);
}
return vbox;
}
/* ----- items ------------------------------------------------------------- */
static void set_item_color(struct inst *inst, const char *color)
{
GtkWidget *label;
if (inst->vec)
label = inst->vec->list_widget;
else
label = inst->obj->list_widget;
if (label)
label_in_box_bg(box_of_label(label), color);
}
void gui_frame_select_inst(struct inst *inst)
{
set_item_color(inst, COLOR_ITEM_SELECTED);
}
void gui_frame_deselect_inst(struct inst *inst)
{
set_item_color(inst, COLOR_ITEM_NORMAL);
}
static gboolean item_select_vec(GtkWidget *widget, GdkEventButton *event,
gpointer data)
{
struct vec *vec = data;
switch (event->button) {
case 1:
inst_select_vec(vec);
redraw();
break;
}
return TRUE;
}
static gboolean item_select_obj(GtkWidget *widget, GdkEventButton *event,
gpointer data)
{
struct obj *obj = data;
switch (event->button) {
case 1:
inst_select_obj(obj);
redraw();
break;
}
return TRUE;
}
static GtkWidget *item_label(GtkWidget *tab, char *s, int col, int row,
gboolean (*cb)(GtkWidget *widget, GdkEventButton *event, gpointer data),
gpointer data)
{
GtkWidget *label;
label = label_in_box_new(s, "Click to select.");
gtk_misc_set_padding(GTK_MISC(label), 0, 0);
gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
gtk_widget_modify_font(label, item_list_font);
gtk_table_attach_defaults(GTK_TABLE(tab), box_of_label(label),
col, col+1, row, row+1);
label_in_box_bg(box_of_label(label), COLOR_ITEM_NORMAL);
if (cb)
g_signal_connect(G_OBJECT(box_of_label(label)),
"button_press_event", G_CALLBACK(cb), data);
free(s);
return label;
}
static GtkWidget *build_items(struct frame *frame)
{
GtkWidget *vbox, *hbox, *tab;
struct order *order, *item;
struct vec *vec;
struct obj *obj;
int n;
char *s, *t;
n = 0;
for (vec = frame->vecs; vec; vec = vec->next)
n++;
for (obj = frame->objs; obj; obj = obj->next)
if (obj->type != ot_meas)
n++;
vbox = gtk_vbox_new(FALSE, 0);
add_sep(vbox, 3);
hbox = gtk_hbox_new(FALSE, 0);
gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
tab = gtk_table_new(n, 2, FALSE);
gtk_box_pack_start(GTK_BOX(hbox), tab, FALSE, FALSE, 0);
order = order_frame(frame);
n = 0;
for (item = order; item->vec || item->obj; item++) {
if (item->obj) {
s = print_obj(item->obj, item->vec);
item->obj->list_widget = item_label(tab, s, 1, n,
item_select_obj, item->obj);
if (item->obj == instantiation_error)
label_in_box_fg(item->obj->list_widget,
COLOR_ITEM_ERROR);
} else {
t = stralloc_printf("%s: ", print_label(item->vec));
item_label(tab, t, 0, n, NULL, NULL);
s = print_vec(item->vec);
item->vec->list_widget = item_label(tab, s, 1, n,
item_select_vec, item->vec);
if (item->vec == instantiation_error)
label_in_box_fg(item->vec->list_widget,
COLOR_ITEM_ERROR);
}
n++;
}
free(order);
return vbox;
}
static GtkWidget *build_meas(struct frame *frame)
{
GtkWidget *vbox, *hbox, *tab;
struct obj *obj;
int n;
char *s;
n = 0;
for (obj = frame->objs; obj; obj = obj->next)
if (obj->type == ot_meas)
n++;
vbox = gtk_vbox_new(FALSE, 0);
add_sep(vbox, 3);
hbox = gtk_hbox_new(FALSE, 0);
gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
tab = gtk_table_new(n, 2, FALSE);
gtk_box_pack_start(GTK_BOX(hbox), tab, FALSE, FALSE, 0);
n = 0;
for (obj = frame->objs; obj; obj = obj->next) {
if (obj->type != ot_meas)
continue;
s = print_meas(obj);
obj->list_widget = item_label(tab, s, 0, n,
item_select_obj, obj);
if (obj == instantiation_error)
label_in_box_fg(obj->list_widget, COLOR_ITEM_ERROR);
n++;
}
return vbox;
}
static void dont_build_items(struct frame *frame)
{
struct vec *vec;
struct obj *obj;
for (vec = frame->vecs; vec; vec = vec->next)
vec->list_widget = NULL;
for (obj = frame->objs; obj; obj = obj->next)
obj->list_widget = NULL;
}
/* ----- package name ------------------------------------------------------ */
static int validate_pkg_name(const char *s, void *ctx)
{
if (!*s)
return 0;
while (*s) {
if (*s < 32 || *s > 126)
return 0;
s++;
}
return 1;
}
static void unselect_pkg_name(void *data)
{
GtkWidget *widget = data;
label_in_box_bg(widget, COLOR_PART_NAME);
}
static gboolean pkg_name_edit_event(GtkWidget *widget, GdkEventButton *event,
gpointer data)
{
switch (event->button) {
case 1:
inst_select_outside(widget, unselect_pkg_name);
label_in_box_bg(widget, COLOR_PART_NAME_EDITING);
status_set_type_entry(NULL, "package =");
status_set_name("Package name (actual)", "%s", pkg_name);
edit_nothing();
edit_name(&pkg_name, validate_pkg_name, NULL,
"Package name (template)");
break;
}
return TRUE;
}
static GtkWidget *build_pkg_name(void)
{
GtkWidget *label;
label = label_in_box_new(pkg_name,
"Package name. (Template) Click to edit.");
gtk_misc_set_padding(GTK_MISC(label), 2, 2);
gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
label_in_box_bg(label, COLOR_PART_NAME);
g_signal_connect(G_OBJECT(box_of_label(label)),
"button_press_event", G_CALLBACK(pkg_name_edit_event), NULL);
return box_of_label(label);
}
/* ----- packages ---------------------------------------------------------- */
static gboolean pkg_scroll_event(GtkWidget *widget, GdkEventScroll *event,
gpointer data)
{
struct pkg *pkg, *last;
switch (event->direction) {
case GDK_SCROLL_UP:
if (active_pkg->next)
active_pkg = active_pkg->next;
else
active_pkg = pkgs->next;
change_world();
break;
case GDK_SCROLL_DOWN:
last = NULL;
for (pkg = pkgs->next; pkg && (!last || pkg != active_pkg);
pkg = pkg->next)
last = pkg;
active_pkg = last;
change_world();
break;
default:
/* ignore */;
}
return TRUE;
}
static gboolean pkg_select_event(GtkWidget *widget, GdkEventButton *event,
gpointer data)
{
struct pkg *pkg = data;
switch (event->button) {
case 1:
active_pkg = pkg;
/* @@@ we could actually skip instantiation here */
change_world();
break;
}
return TRUE;
}
static GtkWidget *build_pkg_names(void)
{
GtkWidget *hbox;
struct pkg *pkg;
GtkWidget *field;
hbox = gtk_hbox_new(FALSE, 0);
for (pkg = pkgs; pkg; pkg = pkg->next)
if (pkg->name) {
field = add_activator(hbox, pkg == active_pkg,
pkg_select_event, pkg,
"Package name. Click to make active.",
"%s", pkg->name);
g_signal_connect(G_OBJECT(box_of_label(field)),
"scroll_event",
G_CALLBACK(pkg_scroll_event), NULL);
}
return hbox;
}
/* ----- frame labels ------------------------------------------------------ */
static int validate_frame_name(const char *s, void *ctx)
{
struct frame *f;
if (!is_id(s))
return 0;
for (f = frames->next; f; f = f->next)
if (!strcmp(f->name, s))
return 0;
return 1;
}
static void unselect_frame(void *data)
{
struct frame *frame= data;
/*
* "unselect" means in this context that the selection has moved
* elsewhere. However, this does not necessarily change the frame.
* (And, in fact, since we rebuild the frame list anyway, the color
* change here doesn't matter if selecting a different frame.)
* So we revert from "editing" to "selected".
*/
label_in_box_bg(frame->label, COLOR_FRAME_SELECTED);
}
static void edit_frame(struct frame *frame)
{
const char *tip;
inst_select_outside(frame, unselect_frame);
label_in_box_bg(frame->label, COLOR_FRAME_EDITING);
tip = "Frame name";
status_set_type_entry(NULL, "name =");
status_set_name(tip, "%s", frame->name);
edit_nothing();
edit_unique(&frame->name, validate_frame_name, frame, tip);
}
void select_frame(struct frame *frame)
{
if (active_frame)
label_in_box_bg(active_frame->label, COLOR_FRAME_UNSELECTED);
active_frame = frame;
change_world();
}
static gboolean frame_press_event(GtkWidget *widget, GdkEventButton *event,
gpointer data)
{
struct frame *frame = data;
switch (event->button) {
case 3:
pop_up_frame(frame, event);
return TRUE;
}
return FALSE;
}
static gboolean frame_release_event(GtkWidget *widget, GdkEventButton *event,
gpointer data)
{
struct frame *frame = data;
switch (event->button) {
case 1:
if (is_dragging(frame))
return FALSE;
if (active_frame != frame) {
select_frame(frame);
} else {
if (active_frame->name) {
edit_nothing();
edit_frame(frame);
}
}
return TRUE;
}
return FALSE;
}
static GtkWidget *build_frame_label(struct frame *frame)
{
GtkWidget *label;
label = label_in_box_new(frame->name ? frame->name : "(root)",
frame->name ? "Frame name. Click to select or edit." :
"Root frame. Click to select.");
gtk_misc_set_padding(GTK_MISC(label), 2, 2);
gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
label_in_box_bg(label, active_frame == frame ?
COLOR_FRAME_SELECTED : COLOR_FRAME_UNSELECTED);
g_signal_connect(G_OBJECT(box_of_label(label)),
"button_press_event", G_CALLBACK(frame_press_event), frame);
g_signal_connect(G_OBJECT(box_of_label(label)),
"button_release_event", G_CALLBACK(frame_release_event), frame);
frame->label = label;
if (frame != frames)
setup_frame_drag(frame);
return box_of_label(label);
}
/* ----- frame references -------------------------------------------------- */
static gboolean frame_ref_select_event(GtkWidget *widget, GdkEventButton *event,
gpointer data)
{
struct obj *obj = data;
switch (event->button) {
case 1:
obj->u.frame.ref->active_ref = data;
change_world();
break;
}
return TRUE;
}
static GtkWidget *build_frame_refs(const struct frame *frame)
{
GtkWidget *hbox;
struct obj *obj;
char *tooltip;
hbox = gtk_hbox_new(FALSE, 0);
for (obj = frame->objs; obj; obj = obj->next)
if (obj->type == ot_frame &&
obj->u.frame.ref == active_frame) {
tooltip = stralloc_printf(
"Frame <b>%s</b> is referenced here. "
"Click to make active.", active_frame->name);
add_activator(hbox,
obj == obj->u.frame.ref->active_ref,
frame_ref_select_event, obj,
tooltip,
"%d", obj->u.frame.lineno);
free(tooltip);
}
return hbox;
}
/* ----- frames ------------------------------------------------------------ */
void build_frames(GtkWidget *vbox, int wrap_width)
{
struct frame *frame;
GtkWidget *hbox, *tab, *label, *packages, *refs, *vars, *items, *meas;
int n = 0;
int max_name_width, name_width;
destroy_all_children(GTK_CONTAINER(vbox));
for (frame = frames; frame; frame = frame->next)
n++;
hbox = gtk_hbox_new(FALSE, 0);
tab = gtk_table_new(n*2+3, 2, FALSE);
gtk_table_set_row_spacings(GTK_TABLE(tab), 1);
gtk_table_set_col_spacings(GTK_TABLE(tab), 1);
gtk_box_pack_start(GTK_BOX(hbox), tab, FALSE, FALSE, 0);
gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
label = build_pkg_name();
gtk_table_attach_defaults(GTK_TABLE(tab), label, 0, 1, 0, 1);
max_name_width = get_widget_width(label);
packages = build_pkg_names();
gtk_table_attach_defaults(GTK_TABLE(tab), packages, 1, 2, 0, 1);
n = 0;
for (frame = frames; frame; frame = frame->next) {
label = build_frame_label(frame);
gtk_table_attach_defaults(GTK_TABLE(tab), label,
0, 1, n*2+1, n*2+2);
n++;
name_width = get_widget_width(label);
if (name_width > max_name_width)
max_name_width = name_width;
}
wrap_width -= max_name_width+FRAME_AREA_MISC_WIDTH;
n = 0;
for (frame = frames; frame; frame = frame->next) {
refs = build_frame_refs(frame);
gtk_table_attach_defaults(GTK_TABLE(tab), refs,
1, 2, n*2+1, n*2+2);
if (show_vars) {
vars = build_vars(frame, wrap_width);
gtk_table_attach_defaults(GTK_TABLE(tab), vars,
1, 2, n*2+2, n*2+3);
dont_build_items(frame);
} else {
items = build_items(frame);
gtk_table_attach_defaults(GTK_TABLE(tab), items,
1, 2, n*2+2, n*2+3);
}
n++;
}
if (!show_vars) {
meas = build_meas(frames);
gtk_table_attach_defaults(GTK_TABLE(tab), meas,
1, 2, n*2+2, n*2+3);
}
gtk_widget_show_all(hbox);
}