dialog-define-names.c 15.1 KB
Newer Older
1
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
Jody Goldberg's avatar
Jody Goldberg committed
2 3 4
/* vim: set sw=8: */
/*
 * dialog-define-name.c: Edit named regions.
Miguel de Icaza's avatar
Miguel de Icaza committed
5 6
 *
 * Author:
7
 *	Jody Goldberg <jgoldberg@home.com>
Jody Goldberg's avatar
Jody Goldberg committed
8
 *	Michael Meeks <michael@imaginator.com>
9
 *	Chema Celorio <chema@celorio.com>
Jody Goldberg's avatar
Jody Goldberg committed
10
 */
Miguel de Icaza's avatar
Miguel de Icaza committed
11
#include <config.h>
12 13 14
#include <glib.h>
#include <libgnome/gnome-defs.h>
#include <libgnome/gnome-i18n.h>
Michael Meeks's avatar
Michael Meeks committed
15
#include <glade/glade.h>
Miguel de Icaza's avatar
Miguel de Icaza committed
16
#include "dialogs.h"
17
#include "expr.h"
18
#include "str.h"
19
#include "expr-name.h"
Jody Goldberg's avatar
Jody Goldberg committed
20
#include "sheet.h"
21
#include "workbook.h"
22
#include "workbook-control.h"
23
#include "workbook-edit.h"
24
#include "gui-util.h"
Jody Goldberg's avatar
Jody Goldberg committed
25
#include "parse-util.h"
26
#include "widgets/gnumeric-expr-entry.h"
Miguel de Icaza's avatar
Miguel de Icaza committed
27

28
#define LIST_KEY "name_list_data"
29
#define DEFINE_NAMES_KEY "define-names-dialog"
30

Michael Meeks's avatar
Michael Meeks committed
31 32
typedef struct {
	GladeXML  *gui;
33
	GtkWidget *dialog;
34 35
	GtkList   *list;
	GtkEntry  *name;
36
	GnumericExprEntry *expr_text;
37
	GtkToggleButton *sheet_scope;
38
	GtkToggleButton *wb_scope;
Michael Meeks's avatar
Michael Meeks committed
39
	GList     *expr_names;
40
	NamedExpression *cur_name;
Jody Goldberg's avatar
Jody Goldberg committed
41

42 43 44 45
	GtkWidget *ok_button;
	GtkWidget *add_button;
	GtkWidget *close_button;
	GtkWidget *delete_button;
46
	GtkWidget *update_button;
47

48
	Sheet	  *sheet;
49 50
	Workbook  *wb;
	WorkbookControlGUI  *wbcg;
51
	ParsePos   pp;
52 53

	gboolean updating;
54
} NameGuruState;
Michael Meeks's avatar
Michael Meeks committed
55

56 57 58 59 60 61
static gboolean
name_guru_scope_is_sheet (NameGuruState *state)
{
	return gtk_toggle_button_get_active (state->sheet_scope);
}

62 63
/**
 * name_guru_warned_if_used:
64 65
 * @state:
 *
66 67
 * If the expresion that is about to be deleted is beeing used,
 * warn the user about it. Ask if we should procede or not
68
 *
69 70 71 72 73 74 75
 * Return Value: TRUE if users confirms deletion, FALSE otherwise
 **/
static gboolean
name_guru_warn (NameGuruState *state)
{
	return TRUE;
}
76

77
static void
78
cb_scope_changed (GtkToggleButton *button, NameGuruState *state)
79
{
80
	if (state->updating || state->cur_name == NULL)
81
		return;
82 83
	expr_name_set_scope (state->cur_name,
		name_guru_scope_is_sheet (state) ? state->sheet : NULL);
84 85 86
}

static void
87
name_guru_display_scope (NameGuruState *state)
88
{
89
	NamedExpression const *nexpr = state->cur_name;
90

91
	state->updating = TRUE;
92 93 94 95
	if (nexpr == NULL || nexpr->pos.sheet == NULL)
		gtk_toggle_button_set_active (state->wb_scope, TRUE);
	else
		gtk_toggle_button_set_active (state->sheet_scope, TRUE);
96
	state->updating = FALSE;
97 98
}

99 100 101 102
static void cb_name_guru_select_name (GtkWidget *list, NameGuruState *state);

/**
 * name_guru_set_expr:
103
 * @state:
104
 * @expr_name: Expression to set in the entries, NULL to clear entries
105
 *
106 107 108 109 110 111 112
 * Set the entries in the dialog from an NamedExpression
 **/
static void
name_guru_set_expr (NameGuruState *state, NamedExpression *expr_name)
{
	state->updating = TRUE;
	if (expr_name) {
113 114
		gchar *txt;

115 116 117
		/* Display the name */
		gtk_entry_set_text (state->name, expr_name->name->str);

Jody Goldberg's avatar
Jody Goldberg committed
118
		/* Display the expr_text */
119
		txt = expr_name_as_string (expr_name, &state->pp);
120
		gtk_entry_set_text (GTK_ENTRY (state->expr_text), txt);
121 122 123
		g_free (txt);
	} else {
		gtk_entry_set_text (state->name, "");
124
		gtk_entry_set_text (GTK_ENTRY (state->expr_text), "");
125 126 127
	}
	state->updating = FALSE;

128
	name_guru_display_scope (state);
129 130 131 132
}

/**
 * name_guru_clear_selection:
133 134
 * @state:
 *
135 136 137 138 139 140 141 142 143 144 145 146 147 148
 * Clear the selection of the gtklist
 **/
static void
name_guru_clear_selection (NameGuruState *state)
{
	g_return_if_fail (state != NULL);

	state->updating = TRUE;
	gtk_list_unselect_all (state->list);
	state->updating = FALSE;
}

/**
 * name_guru_in_list:
149
 * @name:
150
 * @state:
151
 *
152
 * Given a name, it searches for it inside the list of Names
153
 *
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181
 * Return Value: TRUE if name is already defined, FALSE otherwise
 **/
static gboolean
name_guru_in_list (const gchar *name, NameGuruState *state)
{
	NamedExpression *expression;
	GList *list;

	g_return_val_if_fail (name != NULL, FALSE);
	g_return_val_if_fail (state != NULL, FALSE);

	for (list = state->expr_names; list; list = list->next) {
		expression = (NamedExpression *) list->data;
		g_return_val_if_fail (expression != NULL, FALSE);
		g_return_val_if_fail (expression->name != NULL, FALSE);
		g_return_val_if_fail (expression->name->str != NULL, FALSE);
		if (strcasecmp (name, expression->name->str) == 0) {
			return TRUE;
		}
	}

	return FALSE;
}



/**
 * name_guru_update_sensitivity:
182 183 184
 * @state:
 * @update_entries:
 *
185 186 187 188 189 190 191 192 193
 * Update the dialog widgets sensitivity
 **/
static void
name_guru_update_sensitivity (NameGuruState *state, gboolean update_entries)
{
	gboolean selection;
	gboolean update;
	gboolean add;
	gboolean in_list = FALSE;
Jody Goldberg's avatar
Jody Goldberg committed
194 195
	char const *expr_text;
	char const *name;
196 197 198 199 200

	g_return_if_fail (state->list != NULL);

	if (state->updating)
		return;
201

202
	name  = gtk_entry_get_text (state->name);
203
	expr_text = gtk_entry_get_text (GTK_ENTRY (state->expr_text));
204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227

	/** Add is active if :
	 *  - We have a name in the entry to add
	 *  - Either we don't have a current Name or if we have a current
	 *     name, the name is different than what we are going to add
	 **/
	add = (name != NULL &&
	       name[0] != '\0' &&
	       !(in_list = name_guru_in_list (name, state)));
	selection = (g_list_length (state->list->selection) > 0);
	update = (name && *name && !add);

	gtk_widget_set_sensitive (state->delete_button, selection && in_list);
	gtk_widget_set_sensitive (state->add_button,    add);
	gtk_widget_set_sensitive (state->update_button, update);

	if (!selection && update_entries)
		name_guru_set_expr (state, NULL);

	if (selection && !in_list)
		name_guru_clear_selection (state);
}

/**
228
 * cb_name_guru_update_sensitivity:
229 230 231
 * @dummy:
 * @state:
 *
232 233
 **/
static void
234
cb_name_guru_update_sensitivity (GtkWidget *dummy, NameGuruState *state)
235 236 237 238 239 240
{
	name_guru_update_sensitivity (state, FALSE);
}

/**
 * cb_name_guru_select_name:
241 242 243
 * @list:
 * @state:
 *
244 245
 * Set the expression from the selected row in the gtklist
 **/
246
static void
Jody Goldberg's avatar
Jody Goldberg committed
247
cb_name_guru_select_name (GtkWidget *list, NameGuruState *state)
248
{
Jody Goldberg's avatar
Jody Goldberg committed
249
	NamedExpression *expr_name;
Morten Welinder's avatar
Morten Welinder committed
250
	GList *sel = GTK_LIST (list)->selection;
251

252
	if (sel == NULL || state->updating)
253 254 255 256
		return;

	g_return_if_fail (sel->data != NULL);

Jody Goldberg's avatar
Jody Goldberg committed
257
	expr_name = gtk_object_get_data (GTK_OBJECT (sel->data), LIST_KEY);
258

259
	g_return_if_fail (expr_name != NULL);
Jody Goldberg's avatar
Jody Goldberg committed
260 261 262
	g_return_if_fail (expr_name->name != NULL);
	g_return_if_fail (expr_name->name->str != NULL);

263 264
	state->cur_name = expr_name;

265 266
	name_guru_set_expr (state, expr_name);
	name_guru_update_sensitivity (state, FALSE);
Michael Meeks's avatar
Michael Meeks committed
267 268
}

269

Michael Meeks's avatar
Michael Meeks committed
270
static void
Jody Goldberg's avatar
Jody Goldberg committed
271
name_guru_populate_list (NameGuruState *state)
Michael Meeks's avatar
Michael Meeks committed
272 273
{
	GList *names;
Jody Goldberg's avatar
Jody Goldberg committed
274
	GtkContainer *list;
Michael Meeks's avatar
Michael Meeks committed
275

276 277
	g_return_if_fail (state != NULL);
	g_return_if_fail (state->list != NULL);
Michael Meeks's avatar
Michael Meeks committed
278

279
	state->cur_name = NULL;
Michael Meeks's avatar
Michael Meeks committed
280

281 282
	gtk_list_clear_items (state->list, 0, -1);

Morten Welinder's avatar
Morten Welinder committed
283
	g_list_free (state->expr_names);
284
	state->expr_names = sheet_get_available_names (state->sheet);
285

286
	list = GTK_CONTAINER (state->list);
287
	for (names = state->expr_names ; names != NULL ; names = g_list_next (names)) {
288
		NamedExpression *expr_name = names->data;
289
		GtkWidget *li;
290
		if (expr_name->pos.sheet != NULL) {
291
			char *name = g_strdup_printf ("%s!%s",
292
						      expr_name->pos.sheet->name_unquoted,
293 294 295 296 297
						      expr_name->name->str);
			li = gtk_list_item_new_with_label (name);
			g_free (name);
		} else
			li = gtk_list_item_new_with_label (expr_name->name->str);
298
		gtk_object_set_data (GTK_OBJECT (li), LIST_KEY, expr_name);
Jody Goldberg's avatar
Jody Goldberg committed
299
		gtk_container_add (list, li);
Michael Meeks's avatar
Michael Meeks committed
300
	}
Jody Goldberg's avatar
Jody Goldberg committed
301
	gtk_widget_show_all (GTK_WIDGET (state->list));
302
	name_guru_update_sensitivity (state, TRUE);
303 304
}

305 306
/**
 * cb_name_guru_remove:
307 308 309
 * @ignored:
 * @state:
 *
310 311
 * Remove the state->cur_name
 **/
312
static void
313
cb_name_guru_remove (GtkWidget *ignored, NameGuruState *state)
Michael Meeks's avatar
Michael Meeks committed
314
{
315
	g_return_if_fail (state != NULL);
316
	g_return_if_fail (state->cur_name != NULL);
317

318 319
	if (!name_guru_warn (state))
		return;
320

321 322 323
	state->expr_names = g_list_remove (state->expr_names, state->cur_name);
	expr_name_remove (state->cur_name);
	state->cur_name = NULL;
324

325
	name_guru_populate_list (state);
326 327
}

328 329
/**
 * cb_name_guru_add:
330 331
 * @state:
 *
332
 * Update or add a NamedExpression from the values in the gtkentries.
333
 *
334 335
 * Return Value: FALSE if the expression was invalid, TRUE otherwise
 **/
336
static gboolean
337
cb_name_guru_add (NameGuruState *state)
338
{
339
	NamedExpression *expr_name;
340
	ParseError    perr;
341
	ExprTree *expr;
Jody Goldberg's avatar
Jody Goldberg committed
342
	char const *name, *expr_text, *tmp;
343
	gboolean dirty = FALSE;
344 345 346

	g_return_val_if_fail (state != NULL, FALSE);

347
	expr_text = gtk_entry_get_text (GTK_ENTRY (state->expr_text));
348 349 350 351
	name  = gtk_entry_get_text (state->name);

	if (!name || (name[0] == '\0'))
		return TRUE;
352

353
	expr_name = expr_name_lookup (&state->pp, name);
Jody Goldberg's avatar
Jody Goldberg committed
354

355
	/* strip off optional preceding '=' */
Jody Goldberg's avatar
Jody Goldberg committed
356 357
	if (NULL != (tmp = gnumeric_char_start_expr_p (expr_text)))
		expr_text = tmp;
358
	expr = expr_parse_string (expr_text, &state->pp, NULL, &perr);
Jody Goldberg's avatar
Jody Goldberg committed
359

Jody Goldberg's avatar
Jody Goldberg committed
360 361
	/* If the expression is invalid */
	if (expr == NULL) {
362
		gnumeric_notice (state->wbcg, GNOME_MESSAGE_BOX_ERROR, perr.message);
Jody Goldberg's avatar
Jody Goldberg committed
363
		gtk_widget_grab_focus (GTK_WIDGET (state->expr_text));
364
		parse_error_free (&perr);
Jody Goldberg's avatar
Jody Goldberg committed
365 366
		return FALSE;
	} else if (expr_name) {
Jody Goldberg's avatar
Jody Goldberg committed
367
		if (!expr_name->builtin) {
368
			/* This means that the expresion was updated.
369
			 * FIXME: if the scope has been changed too, call scope
370
			 * chaned first.
371
			 */
372 373
			expr_name_set_expr (expr_name, expr);
			dirty = TRUE;
374
		} else
375
			gnumeric_notice (state->wbcg, GNOME_MESSAGE_BOX_ERROR,
Morten Welinder's avatar
Morten Welinder committed
376
					 _("You cannot redefine a builtin name."));
377
	} else {
378
		char const *error = NULL;
379 380 381 382 383
		ParsePos pos;
		if (name_guru_scope_is_sheet (state))
			parse_pos_init (&pos, NULL, state->sheet,
					state->pp.eval.col,
					state->pp.eval.row);
384
		else
385 386 387
			parse_pos_init (&pos, state->wb, NULL,
					state->pp.eval.col,
					state->pp.eval.row);
388

389
		expr_name = expr_name_add (&pos, name, expr, &error);
390 391 392 393 394 395
		if (expr_name == NULL) {
			g_return_val_if_fail (error != NULL, FALSE);
			gnumeric_notice (state->wbcg, GNOME_MESSAGE_BOX_ERROR, error);
			gtk_widget_grab_focus (GTK_WIDGET (state->expr_text));
			return FALSE;
		}
396
		dirty = TRUE;
397
	}
398

Jody Goldberg's avatar
Jody Goldberg committed
399
	g_return_val_if_fail (expr_name != NULL, FALSE);
400

Jody Goldberg's avatar
Jody Goldberg committed
401
	name_guru_populate_list (state);
402
	gtk_widget_grab_focus (GTK_WIDGET (state->name));
403

404 405 406
	if (dirty)
		sheet_set_dirty (state->sheet, TRUE);

407 408 409 410
	return TRUE;
}

static void
411
cb_name_guru_clicked (GtkWidget *button, NameGuruState *state)
412
{
413 414 415
	if (state->dialog == NULL)
		return;

416
	wbcg_set_entry (state->wbcg, NULL);
417

418
	if (button == state->delete_button) {
419
		cb_name_guru_remove (NULL, state);
420 421 422
		return;
	}

423 424 425 426 427
	if (button == state->add_button ||
	    button == state->update_button ||
	    button == state->ok_button) {
		/* If adding the name failed, do not exit */
		if (!cb_name_guru_add (state)) {
Jody Goldberg's avatar
Jody Goldberg committed
428
			return;
429 430
		}
	}
431

432
	if (button == state->close_button || button == state->ok_button) {
433
		gtk_widget_destroy (state->dialog);
434 435
		return;
	}
436

437 438
}

439 440
static GtkWidget *
name_guru_init_button (NameGuruState *state, char const *name)
441
{
442
	GtkWidget *tmp = glade_xml_get_widget (state->gui, name);
443 444

	g_return_val_if_fail (tmp != NULL, NULL);
445

446 447 448 449
	gtk_signal_connect (GTK_OBJECT (tmp), "clicked",
			    GTK_SIGNAL_FUNC (cb_name_guru_clicked),
			    state);
	return tmp;
450 451
}

452 453
static gboolean
cb_name_guru_destroy (GtkObject *w, NameGuruState *state)
454
{
455 456 457
	g_return_val_if_fail (w != NULL, FALSE);
	g_return_val_if_fail (state != NULL, FALSE);

458
	wbcg_edit_detach_guru (state->wbcg);
459 460 461 462 463 464

	if (state->gui != NULL) {
		gtk_object_unref (GTK_OBJECT (state->gui));
		state->gui = NULL;
	}

465
	wbcg_edit_finish (state->wbcg, FALSE);
466 467 468

	state->dialog = NULL;

Morten Welinder's avatar
Morten Welinder committed
469 470 471
	g_list_free (state->expr_names);
	state->expr_names = NULL;

472
	g_free (state);
473

474
	return FALSE;
475 476
}

477 478
static void
cb_name_guru_set_focus (GtkWidget *window, GtkWidget *focus_widget,
479
			NameGuruState *state)
480
{
Jody Goldberg's avatar
Jody Goldberg committed
481
	if (IS_GNUMERIC_EXPR_ENTRY (focus_widget)) {
482
		wbcg_set_entry (state->wbcg,
483 484 485
				    GNUMERIC_EXPR_ENTRY (focus_widget));
		gnumeric_expr_entry_set_absolute (state->expr_text);
	} else
486
		wbcg_set_entry (state->wbcg, NULL);
487 488
}

489
static gboolean
490
name_guru_init (NameGuruState *state, WorkbookControlGUI *wbcg)
491
{
492
	Workbook *wb = wb_control_workbook (WORKBOOK_CONTROL (wbcg));
493
	GtkTable *table2;
494 495 496 497 498

	state->wbcg  = wbcg;
	state->wb   = wb;
	state->sheet = wb_control_cur_sheet (WORKBOOK_CONTROL (wbcg));
	state->gui = gnumeric_glade_xml_new (state->wbcg, "names.glade");
499 500 501
        if (state->gui == NULL)
                return TRUE;

502 503 504 505 506
	parse_pos_init (&state->pp, state->wb, state->sheet,
			state->sheet->edit_pos.col,
			state->sheet->edit_pos.row);


507
	state->dialog = glade_xml_get_widget (state->gui, "NameGuru");
508
	table2 = GTK_TABLE (glade_xml_get_widget (state->gui, "table2"));
509
	state->name  = GTK_ENTRY (glade_xml_get_widget (state->gui, "name"));
Jody Goldberg's avatar
Jody Goldberg committed
510
	state->expr_text = GNUMERIC_EXPR_ENTRY (gnumeric_expr_entry_new (state->wbcg));
511 512 513 514 515
	gtk_table_attach (table2, GTK_WIDGET (state->expr_text),
			  1, 2, 1, 2,
			  GTK_EXPAND | GTK_FILL, 0,
			  0, 0);
	gtk_widget_show (GTK_WIDGET (state->expr_text));
516
	state->sheet_scope = GTK_TOGGLE_BUTTON (glade_xml_get_widget (state->gui, "sheet_scope"));
517
	state->wb_scope = GTK_TOGGLE_BUTTON (glade_xml_get_widget (state->gui, "workbook_scope"));
518
	state->list  = GTK_LIST (glade_xml_get_widget (state->gui, "name_list"));
519
	state->expr_names = NULL;
520
	state->cur_name   = NULL;
521
	state->updating   = FALSE;
522

523 524 525 526 527 528
	gtk_label_set_text (GTK_LABEL (GTK_BIN (state->sheet_scope)->child),
		state->sheet->name_unquoted);
	name_guru_display_scope (state);
	gtk_signal_connect (GTK_OBJECT (state->sheet_scope),
		"toggled",
		GTK_SIGNAL_FUNC (cb_scope_changed), state);
529

530 531 532
	state->ok_button     = name_guru_init_button (state, "ok_button");
	state->close_button  = name_guru_init_button (state, "close_button");
	state->add_button    = name_guru_init_button (state, "add_button");
533
	state->delete_button = name_guru_init_button (state, "delete_button");
534
	state->update_button = name_guru_init_button (state, "update_button");
535 536

	gtk_signal_connect (GTK_OBJECT (state->list), "selection_changed",
537
			    GTK_SIGNAL_FUNC (cb_name_guru_select_name), state);
538 539
	gtk_signal_connect (GTK_OBJECT (state->dialog), "set-focus",
			    GTK_SIGNAL_FUNC (cb_name_guru_set_focus), state);
540
	gtk_signal_connect (GTK_OBJECT (state->dialog), "destroy",
Jody Goldberg's avatar
Jody Goldberg committed
541
			    GTK_SIGNAL_FUNC (cb_name_guru_destroy), state);
542
	gtk_signal_connect (GTK_OBJECT (state->name), "changed",
543
			    GTK_SIGNAL_FUNC (cb_name_guru_update_sensitivity), state);
544 545 546
	/* We need to connect after because this is an expresion, and it will
	 * be changed by the mouse selecting a range, update after the entry
	 * is updated with the new text.
547
	 */
Jody Goldberg's avatar
Jody Goldberg committed
548
	gtk_signal_connect_after (GTK_OBJECT (state->expr_text), "changed",
549
				  GTK_SIGNAL_FUNC (cb_name_guru_update_sensitivity), state);
550

551
	gnumeric_editable_enters (GTK_WINDOW (state->dialog),
Morten Welinder's avatar
Morten Welinder committed
552
				  GTK_EDITABLE (state->name));
553
	gnumeric_editable_enters (GTK_WINDOW (state->dialog),
Jody Goldberg's avatar
Jody Goldberg committed
554
				  GTK_EDITABLE (state->expr_text));
555 556
	gnumeric_keyed_dialog (state->wbcg, GTK_WINDOW (state->dialog),
			       DEFINE_NAMES_KEY);
557

558 559
	gnumeric_expr_entry_set_scg (state->expr_text,
				     wb_control_gui_cur_sheet (wbcg));
560
	wbcg_edit_attach_guru (state->wbcg, state->dialog);
561 562

	return FALSE;
Michael Meeks's avatar
Michael Meeks committed
563 564
}

565 566
/**
 * dialog_define_names:
567 568
 * @wbcg:
 *
569 570
 * Create and show the define names dialog.
 **/
Miguel de Icaza's avatar
Miguel de Icaza committed
571
void
572
dialog_define_names (WorkbookControlGUI *wbcg)
Miguel de Icaza's avatar
Miguel de Icaza committed
573
{
574
	NameGuruState *state;
Michael Meeks's avatar
Michael Meeks committed
575

576
	g_return_if_fail (wbcg != NULL);
Michael Meeks's avatar
Michael Meeks committed
577

578
	/* Only one guru per workbook. */
579
	if (wbcg_edit_has_guru (wbcg))
580 581
		return;

582 583 584 585
	/* Only pop up one copy per workbook */
	if (gnumeric_dialog_raise_if_exists (wbcg, DEFINE_NAMES_KEY))
		return;

586
	state = g_new0 (NameGuruState, 1);
587
	if (name_guru_init (state, wbcg)) {
588 589
		gnumeric_notice (wbcg, GNOME_MESSAGE_BOX_ERROR,
				 _("Could not create the Name Guru."));
590
		g_free (state);
Michael Meeks's avatar
Michael Meeks committed
591 592 593
		return;
	}

Jody Goldberg's avatar
Jody Goldberg committed
594
	name_guru_populate_list (state);
595
	gtk_widget_show (state->dialog);
Miguel de Icaza's avatar
Miguel de Icaza committed
596
}