/* * Copyright (c) by Allin Cottrell * * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. * */ #include "gretl.h" #include "treeutils.h" #include "ssheet.h" #include "dlgutils.h" #include "menustate.h" #include "selector.h" #include "session.h" #include "usermat.h" #include "matrix_extra.h" #include #include #include #define SSDEBUG 0 #undef CELLDEBUG typedef enum { SHEET_SUBSAMPLED = 1 << 0, SHEET_SHORT_VARLIST = 1 << 1, SHEET_INSERT_OBS_OK = 1 << 2, SHEET_ADD_OBS_OK = 1 << 3 } SheetFlags; enum { SHEET_AT_END, SHEET_AT_POINT }; typedef struct { GtkWidget *view; GtkWidget *win; GtkWidget *locator; #if 0 GtkWidget *entry; #endif GtkWidget *popup; GtkWidget *save; GtkItemFactory *ifac; GtkCellRenderer *dumbcell; GtkCellRenderer *datacell; gchar location[64]; gretl_matrix *matrix; gretl_matrix *oldmat; char mname[VNAMELEN]; int *varlist; int datacols, datarows; int totcols; int orig_nobs; int added_vars; int orig_main_v; int modified; SheetCmd cmd; SheetFlags flags; guint cid; guint point; } Spreadsheet; #define MATRIX_DIGITS DBL_DIG /* or 6? */ static void sheet_add_var_callback (gpointer data, guint code, GtkWidget *w); static void sheet_add_obs_callback (gpointer data, guint where, GtkWidget *w); static void get_data_from_sheet (GtkWidget *w, Spreadsheet *sheet); static void set_up_sheet_column (GtkTreeViewColumn *column, gint width, gboolean expand); static gint get_data_col_width (void); static int add_data_column (Spreadsheet *sheet); static void create_sheet_cell_renderers (Spreadsheet *sheet); static void set_dataset_locked (gboolean s); static void matrix_fill_callback (gpointer data, guint u, GtkWidget *w); static void matrix_props_callback (gpointer data, guint u, GtkWidget *w); static void matrix_edit_callback (gpointer data, guint u, GtkWidget *w); static int update_sheet_from_matrix (Spreadsheet *sheet); static void size_matrix_window (Spreadsheet *sheet); enum { MATRIX_FILL_IDENTITY, MATRIX_FILL_UNIFORM, MATRIX_FILL_NORMAL }; enum { MATRIX_EDIT_XTX, MATRIX_EDIT_TRANSPOSE, MATRIX_EDIT_CHOLESKY, MATRIX_EDIT_INVERT, MATRIX_EDIT_MULTIPLY, MATRIX_EDIT_DIVIDE }; static GtkItemFactoryEntry sheet_items[] = { { N_("/_Observation"), NULL, NULL, 0, "", GNULL }, { N_("/Observation/_Append obs"), NULL, sheet_add_obs_callback, SHEET_AT_END, NULL, GNULL }, { N_("/Observation/_Insert obs"), NULL, sheet_add_obs_callback, SHEET_AT_POINT, NULL, GNULL }, { N_("/_Variable"), NULL, NULL, 0, "", GNULL }, { N_("/Variable/_Add"), NULL, sheet_add_var_callback, 0, NULL, GNULL } }; static GtkItemFactoryEntry matrix_items[] = { { N_("/_Fill"), NULL, NULL, 0, "", GNULL }, { N_("/Fill/_Identity matrix"), NULL, matrix_fill_callback, MATRIX_FILL_IDENTITY, NULL, GNULL }, { N_("/Fill/_Uniform random"), NULL, matrix_fill_callback, MATRIX_FILL_UNIFORM, NULL, GNULL }, { N_("/Fill/_Normal random"), NULL, matrix_fill_callback, MATRIX_FILL_NORMAL, NULL, GNULL }, { N_("/_Properties"), NULL, NULL, 0, "", GNULL }, { N_("/Properties/_View"), NULL, matrix_props_callback, 0, NULL, GNULL }, { N_("/_Transform"), NULL, NULL, 0, "", GNULL }, { N_("/Transform/_X'X"), NULL, matrix_edit_callback, MATRIX_EDIT_XTX, NULL, GNULL }, { N_("/Transform/_Transpose"), NULL, matrix_edit_callback, MATRIX_EDIT_TRANSPOSE, NULL, GNULL }, { N_("/Transform/_Cholesky"), NULL, matrix_edit_callback, MATRIX_EDIT_CHOLESKY, NULL, GNULL }, { N_("/Transform/_Invert"), NULL, matrix_edit_callback, MATRIX_EDIT_INVERT, NULL, GNULL }, { N_("/Transform/_Multiply by scalar"), NULL, matrix_edit_callback, MATRIX_EDIT_MULTIPLY, NULL, GNULL }, { N_("/Transform/_Divide by scalar"), NULL, matrix_edit_callback, MATRIX_EDIT_DIVIDE, NULL, GNULL }, }; static void sheet_set_modified (Spreadsheet *sheet, gboolean s) { sheet->modified = s; if (sheet->save != NULL) { gtk_widget_set_sensitive(sheet->save, s); } } static void disable_obs_menu (GtkItemFactory *ifac) { GtkWidget *w = gtk_item_factory_get_item(ifac, "/Observation"); gtk_widget_set_sensitive(w, FALSE); } static void disable_insert_obs_item (GtkItemFactory *ifac) { GtkWidget *w = gtk_item_factory_get_item(ifac, "/Observation/Insert obs"); gtk_widget_set_sensitive(w, FALSE); } static int spreadsheet_hide (int i, const DATAINFO *pdinfo) { int ret = 0; if (var_is_scalar(pdinfo, i)) { ret = 1; } else if (var_is_hidden(pdinfo, i)) { ret = 1; } return ret; } static char *single_underscores (char *targ, const char *src) { char *p = targ; while (*src) { if (*src == '_' && *(src + 1) == '_') { src++; *p++ = '_'; } else { *p++ = *src; } src++; } *p = '\0'; return targ; } #if 0 /* complicated, not ready yet */ static void set_matrix_value_label (Spreadsheet *sheet, GtkTreePath *path, GtkTreeViewColumn *column) { char valstr[48]; int i, j; gint *idx; idx = gtk_tree_path_get_indices(path); i = idx[0]; j = atoi(gtk_tree_view_column_get_title(column)) - 1; sprintf(valstr, "%.*g", DBL_DIG, gretl_matrix_get(sheet->matrix, i, j)); gtk_entry_set_text(GTK_ENTRY(sheet->entry), valstr); } #endif static void set_locator_label (Spreadsheet *sheet, GtkTreePath *path, GtkTreeViewColumn *column) { GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(sheet->view)); GtkTreeIter iter; gchar *rstr; const gchar *cstr; char tmp[VNAMELEN]; #if 0 if (sheet->matrix != NULL) { set_matrix_value_label(sheet, path, column); } #endif gtk_tree_model_get_iter(model, &iter, path); gtk_tree_model_get(model, &iter, 0, &rstr, -1); cstr = gtk_tree_view_column_get_title(column); if (sheet->matrix != NULL) { sprintf(sheet->location, "%s, %s", rstr, cstr); } else { single_underscores(tmp, cstr); sprintf(sheet->location, "%s, %s", tmp, rstr); } gtk_statusbar_pop(GTK_STATUSBAR(sheet->locator), sheet->cid); gtk_statusbar_push(GTK_STATUSBAR(sheet->locator), sheet->cid, sheet->location); g_free(rstr); } static void move_to_next_cell (Spreadsheet *sheet, GtkTreePath *path, GtkTreeViewColumn *column) { GtkTreeView *view = GTK_TREE_VIEW(sheet->view); gint nextrow, colnum; nextrow = gtk_tree_path_get_indices(path)[0] + 1; /* go to next row, if possible... */ if (nextrow < sheet->datarows) { GtkTreePath *newpath; gchar pstr[8]; sprintf(pstr, "%d", nextrow); newpath = gtk_tree_path_new_from_string(pstr); if (newpath != NULL) { gtk_tree_view_set_cursor(view, newpath, column, FALSE); set_locator_label(sheet, newpath, column); gtk_tree_path_free(newpath); } } else { /* ...or try the next column */ gpointer p = g_object_get_data(G_OBJECT(column), "colnum"); if (p != NULL && (colnum = GPOINTER_TO_INT(p)) < sheet->datacols) { GtkTreeViewColumn *nextcol = gtk_tree_view_get_column(view, colnum + 1); if (nextcol != NULL) { gtk_tree_view_set_cursor(view, path, nextcol, FALSE); set_locator_label(sheet, path, nextcol); } } } /* couldn't find a "next cell" to go to */ } static gint sheet_cell_edited (GtkCellRendererText *cell, const gchar *path_string, const gchar *user_text, Spreadsheet *sheet) { const gchar *new_text = NULL; int err = 0; if (sheet->matrix == NULL && (!strcmp(user_text, "na") || !strcmp(user_text, "NA"))) { /* allow conversion to "missing" */ new_text = ""; } else { err = check_atof(user_text); if (err) { errbox(gretl_errmsg_get()); } else { new_text = user_text; } } if (!err && new_text != NULL) { GtkTreeView *view = GTK_TREE_VIEW(sheet->view); GtkTreeModel *model = gtk_tree_view_get_model(view); GtkTreeViewColumn *column; GtkTreePath *path; GtkTreeIter iter; gchar *old_text; gint colnum; gtk_tree_view_get_cursor(view, NULL, &column); colnum = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "colnum")); path = gtk_tree_path_new_from_string(path_string); gtk_tree_model_get_iter(model, &iter, path); gtk_tree_model_get(model, &iter, colnum, &old_text, -1); if (old_text != NULL && strcmp(old_text, new_text)) { gtk_list_store_set(GTK_LIST_STORE(model), &iter, colnum, new_text, -1); sheet_set_modified(sheet, TRUE); } move_to_next_cell(sheet, path, column); gtk_tree_path_free(path); g_free(old_text); } return FALSE; } static void spreadsheet_scroll_to_new_col (Spreadsheet *sheet, GtkTreeViewColumn *column) { GtkTreeView *view = GTK_TREE_VIEW(sheet->view); GtkTreePath *path; gchar *pstr; GtkAdjustment *adj; GtkWidget *sw; pstr = g_strdup("0"); path = gtk_tree_path_new_from_string(pstr); gtk_tree_view_set_cursor(view, path, column, TRUE); sw = gtk_widget_get_ancestor(GTK_WIDGET(view), GTK_TYPE_BIN); if (sw != NULL) { adj = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(sw)); gtk_adjustment_set_value(adj, adj->upper); } gtk_tree_path_free(path); g_free(pstr); } static GtkTreeViewColumn * add_treeview_column_with_title (Spreadsheet *sheet, const char *name) { GtkTreeViewColumn *column; gint cols, colnum; column = gtk_tree_view_column_new(); gtk_tree_view_column_set_title(column, name); set_up_sheet_column(column, get_data_col_width(), TRUE); cols = gtk_tree_view_insert_column(GTK_TREE_VIEW(sheet->view), column, -1); colnum = cols - 1; gtk_tree_view_column_pack_start(column, sheet->datacell, TRUE); gtk_tree_view_column_set_attributes(column, sheet->datacell, "text", colnum, NULL); g_object_set_data(G_OBJECT(column), "colnum", GINT_TO_POINTER(colnum)); return column; } static int real_add_new_var (Spreadsheet *sheet, const char *varname) { GtkTreeViewColumn *column; char tmp[32]; if (add_data_column(sheet)) { return 1; } #if SSDEBUG fprintf(stderr, "real_add_new_var, after add_data_column: sheet->totcols=%d\n", sheet->totcols); #endif double_underscores(tmp, varname); column = add_treeview_column_with_title(sheet, tmp); /* scroll to editing position if need be */ spreadsheet_scroll_to_new_col(sheet, column); sheet_set_modified(sheet, TRUE); return 0; } static void spreadsheet_scroll_to_foot (Spreadsheet *sheet, int row, int col) { GtkTreeView *view = GTK_TREE_VIEW(sheet->view); GtkTreePath *path; gchar *pstr; GtkTreeViewColumn *column; GtkAdjustment *adj; GtkWidget *sw; pstr = g_strdup_printf("%d", row); path = gtk_tree_path_new_from_string(pstr); column = gtk_tree_view_get_column(view, col); gtk_tree_view_set_cursor(view, path, column, FALSE); sw = gtk_widget_get_ancestor(GTK_WIDGET(view), GTK_TYPE_BIN); if (sw != NULL) { adj = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(sw)); gtk_adjustment_set_value(adj, adj->upper); } gtk_tree_path_free(path); g_free(pstr); } static void real_add_new_obs (Spreadsheet *sheet, const char *obsname, int n) { GtkTreeView *view = GTK_TREE_VIEW(sheet->view); gint rownum = 0; gint oldrows = sheet->datarows; GtkListStore *store; gchar *pstr = NULL; GtkTreeIter iter; gchar rowlabel[10]; gint i, j; store = GTK_LIST_STORE(gtk_tree_view_get_model(view)); if (sheet->point == SHEET_AT_END) { rownum = sheet->datarows - 1; pstr = g_strdup_printf("%d", rownum); for (i=0; ipoint == SHEET_AT_POINT) { GtkTreePath *path; gtk_tree_view_get_cursor(view, &path, NULL); rownum = gtk_tree_path_get_indices(path)[0]; gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &iter, path); gtk_list_store_insert(store, &iter, rownum); gtk_tree_path_free(path); } else { return; } if (datainfo->markers && obsname != NULL) { gtk_list_store_set(store, &iter, 0, obsname, -1); } else if (sheet->point == SHEET_AT_END) { gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(store), &iter, pstr); for (j=0; jdatarows + j, datainfo); gtk_list_store_set(store, &iter, 0, rowlabel, -1); } } sheet->datarows += n; if (pstr != NULL) { gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(store), &iter, pstr); for (j=0; jdatacols; i++) { gtk_list_store_set(store, &iter, i, "", -1); } } } else { for (i=1; i<=sheet->datacols; i++) { gtk_list_store_set(store, &iter, i, "", -1); } } if (sheet->point == SHEET_AT_POINT && !datainfo->markers) { for (i=rownum; idatarows; i++) { get_full_obs_string(rowlabel, i, datainfo); gtk_list_store_set(store, &iter, 0, rowlabel, -1); gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter); } } if (sheet->point == SHEET_AT_END) { spreadsheet_scroll_to_foot(sheet, oldrows, 1); } else { GtkTreePath *path; GtkTreeIter insiter; pstr = g_strdup_printf("%d", rownum); path = gtk_tree_path_new_from_string(pstr); gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &insiter, path); gtk_list_store_set(store, &insiter, 1, "", -1); gtk_tree_path_free(path); } if (pstr != NULL) { g_free(pstr); } sheet_set_modified(sheet, TRUE); } static void name_new_var (GtkWidget *widget, dialog_t *dlg) { Spreadsheet *sheet = (Spreadsheet *) edit_dialog_get_data(dlg); const gchar *buf; char varname[VNAMELEN]; buf = edit_dialog_get_text(dlg); if (buf == NULL || validate_varname(buf)) return; *varname = 0; strncat(varname, buf, VNAMELEN - 1); close_dialog(dlg); if (real_add_new_var(sheet, varname)) { nomem(); } } static void name_new_obs (GtkWidget *widget, dialog_t *dlg) { Spreadsheet *sheet = (Spreadsheet *) edit_dialog_get_data(dlg); const gchar *buf; char obsmarker[OBSLEN]; buf = edit_dialog_get_text(dlg); if (buf == NULL) return; *obsmarker = 0; strncat(obsmarker, buf, OBSLEN - 1); close_dialog(dlg); real_add_new_obs(sheet, obsmarker, 1); } static void name_var_dialog (Spreadsheet *sheet) { edit_dialog (_("gretl: name variable"), _("Enter name for new variable\n" "(max. 15 characters)"), NULL, name_new_var, sheet, 0, VARCLICK_NONE, NULL); } static void new_case_dialog (Spreadsheet *sheet) { edit_dialog (_("gretl: case marker"), _("Enter case marker for new obs\n" "(max. 8 characters)"), NULL, name_new_obs, sheet, 0, VARCLICK_NONE, NULL); } static GtkListStore *make_sheet_liststore (Spreadsheet *sheet) { GtkListStore *store; GType *types; int i; /* obs col, data cols */ sheet->totcols = sheet->datacols + 1; types = mymalloc(sheet->totcols * sizeof *types); if (types == NULL) { return NULL; } for (i=0; itotcols; i++) { types[i] = G_TYPE_STRING; } store = gtk_list_store_newv(sheet->totcols, types); free(types); return store; } /* This is relatively complex because, so far as I can tell, you can't append or insert additional columns in a GtkListStore: we have to create a whole new liststore and copy the old info across. */ static int add_data_column (Spreadsheet *sheet) { GtkListStore *old_store, *new_store; GtkTreeIter old_iter, new_iter; gint row; sheet->datacols += 1; /* get pointers to original and new stores */ new_store = make_sheet_liststore(sheet); if (new_store == NULL) { sheet->datacols -= 1; sheet->totcols -= 1; return E_ALLOC; } old_store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(sheet->view))); /* go to start of old and new lists */ gtk_tree_model_get_iter_first(GTK_TREE_MODEL(old_store), &old_iter); gtk_tree_model_get_iter_first(GTK_TREE_MODEL(new_store), &new_iter); /* construct the new table */ for (row=0; rowdatarows; row++) { gchar *str; int col; gtk_list_store_append(new_store, &new_iter); for (col=0; coltotcols; col++) { if (col < sheet->datacols) { /* copy labels and original data */ gtk_tree_model_get(GTK_TREE_MODEL(old_store), &old_iter, col, &str, -1); gtk_list_store_set(new_store, &new_iter, col, str, -1); g_free(str); } else { /* new data values: set blank */ gtk_list_store_set(new_store, &new_iter, col, "", -1); } } gtk_tree_model_iter_next(GTK_TREE_MODEL(old_store), &old_iter); } gtk_tree_view_set_model(GTK_TREE_VIEW(sheet->view), GTK_TREE_MODEL(new_store)); g_object_unref(G_OBJECT(new_store)); sheet->added_vars += 1; #if SSDEBUG fprintf(stderr, "add_data_column: sheet->added_vars now = %d\n", sheet->added_vars); #endif return 0; } static void sheet_get_scalar (GtkWidget *w, dialog_t *dlg) { double *x = (double *) edit_dialog_get_data(dlg); const gchar *buf; int err; buf = edit_dialog_get_text(dlg); if (buf == NULL) return; err = check_atof(buf); if (err) { gui_errmsg(err); } else { *x = atof(buf); close_dialog(dlg); } } static void matrix_edit_callback (gpointer data, guint u, GtkWidget *w) { Spreadsheet *sheet = (Spreadsheet *) data; double x = NADBL; int err = 0; if (u == MATRIX_EDIT_MULTIPLY || u == MATRIX_EDIT_DIVIDE) { int cancel = 0; edit_dialog(_("gretl: specify scalar"), _("Enter a numerical value"), NULL, sheet_get_scalar, &x, 0, VARCLICK_NONE, &cancel); if (cancel || na(x)) { return; } } switch (u) { case MATRIX_EDIT_XTX: err = matrix_XTX_in_place(sheet->matrix); break; case MATRIX_EDIT_TRANSPOSE: err = matrix_transpose_in_place(sheet->matrix); break; case MATRIX_EDIT_CHOLESKY: err = matrix_cholesky_in_place(sheet->matrix); break; case MATRIX_EDIT_INVERT: err = matrix_invert_in_place(sheet->matrix); break; case MATRIX_EDIT_MULTIPLY: gretl_matrix_multiply_by_scalar(sheet->matrix, x); break; case MATRIX_EDIT_DIVIDE: gretl_matrix_divide_by_scalar(sheet->matrix, x); break; } if (err) { gui_errmsg(err); } else { update_sheet_from_matrix(sheet); } } static void matrix_props_callback (gpointer data, guint u, GtkWidget *w) { Spreadsheet *sheet = (Spreadsheet *) data; view_matrix_properties(sheet->matrix, sheet->mname); } static void matrix_fill_callback (gpointer data, guint u, GtkWidget *w) { Spreadsheet *sheet = (Spreadsheet *) data; gretl_matrix *A = sheet->matrix; int mindim; switch (u) { case MATRIX_FILL_IDENTITY: mindim = (A->rows < A->cols)? A->rows : A->cols; gretl_matrix_inscribe_I(A, 0, 0, mindim); break; case MATRIX_FILL_UNIFORM: gretl_matrix_random_fill(A, D_UNIFORM); break; case MATRIX_FILL_NORMAL: gretl_matrix_random_fill(A, D_NORMAL); break; } update_sheet_from_matrix(sheet); } static void sheet_add_var_callback (gpointer data, guint u, GtkWidget *w) { Spreadsheet *sheet = (Spreadsheet *) data; name_var_dialog(sheet); } static void sheet_add_obs_callback (gpointer data, guint where, GtkWidget *w) { Spreadsheet *sheet = (Spreadsheet *) data; sheet->point = where; if (datainfo->markers) { new_case_dialog(sheet); } else if (where == SHEET_AT_END) { int n = add_obs_dialog(NULL, 1); if (n > 0) { real_add_new_obs(sheet, NULL, n); } } else { real_add_new_obs(sheet, NULL, 1); } } static void popup_sheet_add_obs (GtkWidget *w, Spreadsheet *sheet) { sheet_add_obs_callback(sheet, SHEET_AT_END, NULL); } static void popup_sheet_insert_obs (GtkWidget *w, Spreadsheet *sheet) { sheet_add_obs_callback(sheet, SHEET_AT_POINT, NULL); } static void popup_sheet_add_var (GtkWidget *w, Spreadsheet *sheet) { sheet_add_var_callback(sheet, 0, NULL); } static void build_sheet_popup (Spreadsheet *sheet) { if (sheet->popup != NULL) return; sheet->popup = gtk_menu_new(); add_popup_item(_("Add Variable"), sheet->popup, G_CALLBACK(popup_sheet_add_var), sheet); if (sheet->flags & SHEET_ADD_OBS_OK) { add_popup_item(_("Add Observation"), sheet->popup, G_CALLBACK(popup_sheet_add_obs), sheet); } if (sheet->flags & SHEET_INSERT_OBS_OK) { add_popup_item(_("Insert Observation"), sheet->popup, G_CALLBACK(popup_sheet_insert_obs), sheet); } } /* the following is connected to the "cursor-changed" signal */ static gboolean update_cell_position (GtkTreeView *view, Spreadsheet *sheet) { GtkTreePath *path = NULL; GtkTreeViewColumn *column; static gint oldrow, oldcol; #if CELLDEBUG fprintf(stderr, "** update_cell_position()\n"); #endif gtk_tree_view_get_cursor(view, &path, &column); if (path != NULL && column != NULL) { gint newrow = gtk_tree_path_get_indices(path)[0]; gint newcol = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(column), "colnum")); if (newcol == 0) { /* not a data column */ gtk_tree_path_free(path); return TRUE; } if (newrow != oldrow || newcol != oldcol) { #if CELLDEBUG fprintf(stderr, " activating cell(%d, %d)\n", newrow, newcol); #endif set_locator_label(sheet, path, column); oldrow = newrow; oldcol = newcol; gtk_tree_view_set_cursor(view, path, column, FALSE); /* start editing? */ } else { #if CELLDEBUG fprintf(stderr, " still in cell(%d, %d)\n", oldrow, oldcol); #endif } } if (path != NULL) { gtk_tree_path_free(path); } return FALSE; /* is this right? (was TRUE) */ } /* put modified values from the spreadsheet into the attached matrix */ static void update_matrix_from_sheet (Spreadsheet *sheet) { GtkTreeView *view = GTK_TREE_VIEW(sheet->view); GtkTreeIter iter; GtkTreeModel *model; gchar *numstr; double x; int i, j; model = gtk_tree_view_get_model(view); gtk_tree_model_get_iter_first(model, &iter); for (i=0; idatarows; i++) { for (j=0; jdatacols; j++) { gtk_tree_model_get(model, &iter, j+1, &numstr, -1); x = atof(numstr); gretl_matrix_set(sheet->matrix, i, j, x); g_free(numstr); } gtk_tree_model_iter_next(model, &iter); } /* in case we're editing a pre-existing matrix, carry the modifications back */ if (sheet->oldmat != NULL) { if (sheet->oldmat->rows == sheet->matrix->rows && sheet->oldmat->cols == sheet->matrix->cols) { gretl_matrix_copy_values(sheet->oldmat, sheet->matrix); } else if (sheet->oldmat->rows * sheet->oldmat->cols == sheet->matrix->rows * sheet->matrix->cols) { sheet->oldmat->rows = sheet->matrix->rows; sheet->oldmat->cols = sheet->matrix->cols; gretl_matrix_copy_values(sheet->oldmat, sheet->matrix); } else { gretl_matrix *m = gretl_matrix_copy(sheet->matrix); if (m == NULL) { nomem(); } else { user_matrix_replace_matrix_by_name(sheet->mname, m); sheet->oldmat = m; } } /* record the fact that a matrix has been changed */ mark_session_changed(); } sheet_set_modified(sheet, FALSE); } /* pull modified values from the data-editing spreadsheet into the main dataset */ static void update_dataset_from_sheet (Spreadsheet *sheet) { GtkTreeView *view = GTK_TREE_VIEW(sheet->view); GtkTreeIter iter; GtkTreeViewColumn *column; GtkTreeModel *model; int oldv = datainfo->v; int newvars = sheet->added_vars; int newobs = sheet->datarows - sheet->orig_nobs; int missobs = 0; int i, colnum, s, t; #if SSDEBUG fprintf(stderr, "get_data_from_sheet: oldv = %d, newvars = %d, newobs = %d\n", oldv, newvars, newobs); #endif model = gtk_tree_view_get_model(view); /* first extend series length, if needed */ if (newobs > 0) { if (dataset_add_observations(newobs, &Z, datainfo, OPT_A) || dataset_destroy_hidden_variables(&Z, datainfo, 0)) { nomem(); return; } } /* then add any new variables to data set */ if (newvars > 0) { int vi, v0 = sheet->varlist[0]; const gchar *newname; if (dataset_add_series(newvars, &Z, datainfo)) { nomem(); return; } for (i=0; ivarlist, vi); if (sheet->varlist == NULL) { nomem(); return; } colnum = v0 + 1 + i; column = gtk_tree_view_get_column(view, colnum); newname = gtk_tree_view_column_get_title(column); strcpy(datainfo->varname[vi], newname); #if SSDEBUG fprintf(stderr, " added var %d (%s) from column %d\n", vi, newname, colnum); #endif } } /* copy data values from spreadsheet */ colnum = 1; for (i=1; i<=sheet->varlist[0]; i++) { int vi = sheet->varlist[i]; gchar *numstr; gtk_tree_model_get_iter_first(model, &iter); #if SSDEBUG fprintf(stderr, " updating data for var %d (%s) from column %d\n", vi, datainfo->varname[vi], colnum); #endif t = datainfo->t1; for (s=0; sdatarows; s++) { gtk_tree_model_get(model, &iter, colnum, &numstr, -1); if (*numstr != '\0') { Z[vi][t++] = atof(numstr); } else { Z[vi][t++] = NADBL; missobs = 1; } g_free(numstr); gtk_tree_model_iter_next(model, &iter); } colnum++; } /* copy observation markers, if relevant */ if (datainfo->markers && datainfo->S != NULL) { gchar *marker; gtk_tree_model_get_iter_first(model, &iter); t = datainfo->t1; for (s=0; sdatarows; s++) { gtk_tree_model_get(model, &iter, 0, &marker, -1); strcpy(datainfo->S[t++], marker); g_free(marker); gtk_tree_model_iter_next(model, &iter); } } data_status |= (GUI_DATA | MODIFIED_DATA); register_data(NULL, NULL, 0); if (missobs) { infobox(_("Warning: there were missing observations")); } sheet_set_modified(sheet, FALSE); sheet->added_vars -= newvars; /* record that these are handled */ } static void matrix_new_name (GtkWidget *w, dialog_t *dlg) { char *newname = (char *) edit_dialog_get_data(dlg); const gchar *buf = edit_dialog_get_text(dlg); if (buf == NULL || validate_varname(buf)) return; *newname = 0; strncat(newname, buf, VNAMELEN - 1); close_dialog(dlg); } static int matrix_overwrite_check (const char *name) { gchar *msg = g_strdup_printf(_("A matrix named %s already exists\n" "OK to overwrite it?"), name); int resp; resp = yes_no_dialog("gretl", msg, 0); g_free(msg); return resp; } static void matrix_save_as (GtkWidget *w, Spreadsheet *sheet) { char newname[VNAMELEN]; int cancel = 0; edit_dialog(_("gretl: save matrix"), _("Enter a name"), NULL, matrix_new_name, newname, 0, VARCLICK_NONE, &cancel); if (!cancel) { user_matrix *u; gchar *tmp; if (get_matrix_by_name(newname) != NULL && matrix_overwrite_check(newname) == GRETL_NO) { return; } sheet->oldmat = gretl_matrix_copy(sheet->matrix); add_or_replace_user_matrix(sheet->oldmat, newname); strcpy(sheet->mname, newname); tmp = g_strdup_printf("gretl: %s", sheet->mname); gtk_window_set_title(GTK_WINDOW(sheet->win), tmp); g_free(tmp); u = get_user_matrix_by_name(newname); g_object_set_data(G_OBJECT(sheet->win), "object", u); sheet_set_modified(sheet, FALSE); } } #define new_matrix(s) (s->cmd == SHEET_EDIT_MATRIX && \ s->oldmat == NULL) static void get_data_from_sheet (GtkWidget *w, Spreadsheet *sheet) { if (!sheet->modified && !new_matrix(sheet)) { infobox(_("No changes were made")); } else if (sheet->cmd == SHEET_EDIT_MATRIX) { update_matrix_from_sheet(sheet); } else { update_dataset_from_sheet(sheet); } } static void select_first_editable_cell (Spreadsheet *sheet) { GtkTreeView *view = GTK_TREE_VIEW(sheet->view); GtkTreePath *path; GtkTreeViewColumn *column; path = gtk_tree_path_new_from_string("0"); column = gtk_tree_view_get_column(view, 1); gtk_tree_view_set_cursor(view, path, column, FALSE); set_locator_label(sheet, path, column); gtk_tree_path_free(path); } static void set_allowable_transforms (Spreadsheet *sheet) { int z = gretl_is_zero_matrix(sheet->matrix); int s = gretl_matrix_get_structure(sheet->matrix); GtkWidget *w; w = gtk_item_factory_get_item(sheet->ifac, "/Transform/Multiply by scalar"); gtk_widget_set_sensitive(w, z == 0); w = gtk_item_factory_get_item(sheet->ifac, "/Transform/Divide by scalar"); gtk_widget_set_sensitive(w, z == 0); w = gtk_item_factory_get_item(sheet->ifac, "/Transform/X'X"); gtk_widget_set_sensitive(w, !(s > 0 && z)); w = gtk_item_factory_get_item(sheet->ifac, "/Transform/Cholesky"); gtk_widget_set_sensitive(w, s == GRETL_MATRIX_SYMMETRIC && z == 0); w = gtk_item_factory_get_item(sheet->ifac, "/Transform/Invert"); gtk_widget_set_sensitive(w, s != 0 && z == 0); w = gtk_item_factory_get_item(sheet->ifac, "/Transform/Transpose"); gtk_widget_set_sensitive(w, s != GRETL_MATRIX_SYMMETRIC && s != GRETL_MATRIX_DIAGONAL && s != GRETL_MATRIX_SCALAR); } static int rejig_sheet_cols (Spreadsheet *sheet) { int n = sheet->matrix->cols - sheet->datacols; GtkTreeViewColumn *column; GtkListStore *store; char rstr[16]; int i; sheet->datacols = sheet->matrix->cols; store = make_sheet_liststore(sheet); if (store == NULL) { return E_ALLOC; } gtk_tree_view_set_model(GTK_TREE_VIEW(sheet->view), GTK_TREE_MODEL(store)); g_object_unref(G_OBJECT(store)); if (n > 0) { for (i=0; idatacols - n + i + 1); add_treeview_column_with_title(sheet, rstr); } } else { n = -n; for (i=0; iview), sheet->datacols + n - i); gtk_tree_view_remove_column(GTK_TREE_VIEW(sheet->view), column); } } return 0; } static int update_sheet_from_matrix (Spreadsheet *sheet) { gchar tmpstr[32]; GtkTreeView *view = GTK_TREE_VIEW(sheet->view); GtkListStore *store; GtkTreeIter iter; int i, j, resize = 0; double x; int err = 0; if (sheet->matrix->cols != sheet->datacols) { err = rejig_sheet_cols(sheet); if (err) { return err; } resize = 1; } store = GTK_LIST_STORE(gtk_tree_view_get_model(view)); gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter); for (i=0; imatrix->rows; i++) { if (resize) { gtk_list_store_append(store, &iter); } if (resize || i >= sheet->datarows) { sprintf(tmpstr, "%d", i + 1); gtk_list_store_set(store, &iter, 0, tmpstr, -1); } for (j=0; jdatacols; j++) { x = gretl_matrix_get(sheet->matrix, i, j); sprintf(tmpstr, "%.*g", MATRIX_DIGITS, x); gtk_list_store_set(store, &iter, j + 1, tmpstr, -1); } if (resize) { continue; } else if (i < sheet->datarows) { gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter); } else { gtk_list_store_append(store, &iter); } } if (!resize) { for (i=sheet->matrix->rows; idatarows; i++) { gtk_list_store_remove(store, &iter); } } if (sheet->datarows != sheet->matrix->rows) { sheet->datarows = sheet->matrix->rows; resize = 1; } sheet_set_modified(sheet, TRUE); set_allowable_transforms(sheet); if (resize) { size_matrix_window(sheet); } return 0; } static int add_matrix_data_to_sheet (Spreadsheet *sheet) { gchar tmpstr[32]; GtkTreeView *view = GTK_TREE_VIEW(sheet->view); GtkTreeIter iter; GtkListStore *store; double x; int i, j; store = GTK_LIST_STORE(gtk_tree_view_get_model(view)); /* row labels */ gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter); for (i=0; idatarows; i++) { sprintf(tmpstr, "%d", i+1); gtk_list_store_append(store, &iter); gtk_list_store_set(store, &iter, 0, tmpstr, -1); } /* now insert data values */ gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter); for (i=0; idatarows; i++) { for (j=0; jdatacols; j++) { x = gretl_matrix_get(sheet->matrix, i, j); sprintf(tmpstr, "%.*g", DBL_DIG, x); gtk_list_store_set(store, &iter, j + 1, tmpstr, -1); } gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter); } set_allowable_transforms(sheet); return 0; } static int add_data_to_sheet (Spreadsheet *sheet, SheetCmd c) { gchar rowlabel[OBSLEN]; GtkTreeView *view = GTK_TREE_VIEW(sheet->view); GtkTreeIter iter; GtkListStore *store; int i, t; #if SSDEBUG fprintf(stderr, "Doing add_data_to_sheet\n"); #endif store = GTK_LIST_STORE(gtk_tree_view_get_model(view)); /* insert observation markers */ gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter); for (t=datainfo->t1; t<=datainfo->t2; t++) { get_full_obs_string(rowlabel, t, datainfo); gtk_list_store_append(store, &iter); gtk_list_store_set(store, &iter, 0, rowlabel, -1); } sheet->datarows = datainfo->t2 - datainfo->t1 + 1; /* now insert data values */ gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter); for (t=datainfo->t1; t<=datainfo->t2; t++) { if (c == SHEET_NEW_DATASET) { /* no hidden vars to consider; insert NAs for first var */ gtk_list_store_set(store, &iter, 1, "", -1); } else { char numstr[32]; int colnum = 0; int vi; for (i=1; i<=sheet->varlist[0]; i++) { vi = sheet->varlist[i]; if (na(Z[vi][t])) { *numstr = '\0'; } else { sprintf(numstr, "%.*g", DBL_DIG, Z[vi][t]); } gtk_list_store_set(store, &iter, ++colnum, numstr, -1); } } gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter); } sheet->orig_main_v = datainfo->v; #if SSDEBUG fprintf(stderr, " datarows=%d, orig_vars=%d, orig_main_v=%d\n", sheet->datarows, sheet->varlist[0], sheet->orig_main_v); #endif return 0; } static gint get_string_width (const gchar *str) { gint width; GtkWidget *w; PangoLayout *pl; PangoContext *pc; w = gtk_label_new(NULL); pc = gtk_widget_get_pango_context(w); pl = pango_layout_new(pc); pango_layout_set_text(pl, str, -1); pango_layout_get_pixel_size(pl, &width, NULL); gtk_widget_destroy(w); g_object_unref(G_OBJECT(pl)); return width; } static gint get_row_label_width (void) { static gint width; if (width == 0) { width = get_string_width("XXXX"); } return width; } static gint get_obs_col_width (void) { static gint width; if (width == 0) { width = get_string_width("XXXXXXXXX"); } return width; } static gint get_data_col_width (void) { static gint width; if (width == 0) { width = get_string_width("-00.0000000000"); } return width; } static void set_up_sheet_column (GtkTreeViewColumn *column, gint width, gboolean expand) { gtk_tree_view_column_set_alignment(column, 0.5); /* header centered */ gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE); gtk_tree_view_column_set_resizable(column, TRUE); gtk_tree_view_column_set_min_width(column, width); gtk_tree_view_column_set_expand(column, expand); } static void create_sheet_cell_renderers (Spreadsheet *sheet) { GtkCellRenderer *r; r = gtk_cell_renderer_text_new(); g_object_set(r, "ypad", 1, "xalign", 1.0, "background", "gray", "editable", FALSE, NULL); sheet->dumbcell = r; r = gtk_cell_renderer_text_new(); g_object_set(r, "ypad", 1, "xalign", 1.0, "editable", TRUE, NULL); g_signal_connect (G_OBJECT (r), "edited", G_CALLBACK(sheet_cell_edited), sheet); sheet->datacell = r; } static void manufacture_keystroke (GtkWidget *widget, guint uval) { GdkKeymapKey *keys; gint n_keys; if (gdk_keymap_get_entries_for_keyval(NULL, uval, &keys, &n_keys)) { guint16 hardware_keycode; GdkEvent *event; hardware_keycode = keys[0].keycode; g_free(keys); event = gdk_event_new(GDK_KEY_PRESS); event->key.window = g_object_ref(widget->window); event->key.hardware_keycode = hardware_keycode; event->key.keyval = gdk_unicode_to_keyval(uval); event->key.length = 1; event->key.send_event = FALSE; event->key.time = GDK_CURRENT_TIME; gtk_main_do_event(event); gdk_event_free(event); } } static gint catch_spreadsheet_key (GtkWidget *view, GdkEventKey *key, Spreadsheet *sheet) { if (key->keyval == GDK_Tab) { /* FIXME */ ; } /* prevent cursor movement outside of the data area */ if (key->keyval == GDK_Right || key->keyval == GDK_Left) { GtkTreeViewColumn *column; gpointer p; gtk_tree_view_get_cursor(GTK_TREE_VIEW(view), NULL, &column); p = g_object_get_data(G_OBJECT(column), "colnum"); if (p != NULL) { int colnum = GPOINTER_TO_INT(p); if (key->keyval == GDK_Left && colnum == 1) { return TRUE; } } } else if (key->keyval == GDK_Up || key->keyval == GDK_Down) { GtkTreePath *path = NULL; GtkTreeViewColumn *column; int i; gtk_tree_view_get_cursor(GTK_TREE_VIEW(view), &path, &column); i = (gtk_tree_path_get_indices(path))[0]; if (key->keyval == GDK_Down && i < sheet->datarows - 1) { gtk_tree_path_next(path); } else if (key->keyval == GDK_Up && i > 0) { gtk_tree_path_prev(path); } gtk_tree_view_set_cursor(GTK_TREE_VIEW(view), path, column, FALSE); gtk_tree_path_free(path); return TRUE; } /* numeric key: start editing */ else if ((key->keyval >= GDK_0 && key->keyval <= GDK_9) || key->keyval == GDK_minus || key->keyval == GDK_period) { GtkTreePath *path = NULL; GtkTreeViewColumn *column; gtk_tree_view_get_cursor(GTK_TREE_VIEW(view), &path, &column); if (path != NULL && column != NULL) { gtk_tree_view_set_cursor(GTK_TREE_VIEW(view), path, column, TRUE); gtk_tree_path_free(path); manufacture_keystroke(view, key->keyval); } } return FALSE; } /* requires gtk 2.2 or higher */ static gint catch_spreadsheet_click (GtkWidget *view, GdkEvent *event, Spreadsheet *sheet) { GdkModifierType mods; gint ret = FALSE; # if CELLDEBUG fprintf(stderr, "** catch_spreadsheet_click()\n"); # endif if (event->type != GDK_BUTTON_PRESS) { return FALSE; } gdk_window_get_pointer(view->window, NULL, NULL, &mods); if (sheet->matrix == NULL && (mods & GDK_BUTTON3_MASK)) { GdkEventButton *bevent = (GdkEventButton *) event; if (sheet->popup == NULL) { build_sheet_popup(sheet); } gtk_menu_popup(GTK_MENU(sheet->popup), NULL, NULL, NULL, NULL, bevent->button, bevent->time); return TRUE; } if (mods & GDK_BUTTON1_MASK) { GdkEventButton *bevent = (GdkEventButton *) event; GtkTreePath *path; GtkTreeViewColumn *column; # if CELLDEBUG fprintf(stderr, "Got button 1 click\n"); # endif gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(sheet->view), (gint) bevent->x, (gint) bevent->y, &path, &column, NULL, NULL); if (column != NULL) { gpointer p = g_object_get_data(G_OBJECT(column), "colnum"); gint colnum = GPOINTER_TO_INT(p); # if CELLDEBUG fprintf(stderr, "Clicked column: colnum = %d\n", colnum); # endif if (colnum == 0) { /* don't respond to a click in a non-data column */ ret = TRUE; } else { /* otherwise start editing on clicked cell */ gtk_tree_view_set_cursor(GTK_TREE_VIEW(sheet->view), path, column, TRUE); ret = TRUE; } } gtk_tree_path_free(path); } # if CELLDEBUG fprintf(stderr, "catch_spreadsheet_click returning %d\n", ret); # endif return ret; } static int build_sheet_view (Spreadsheet *sheet) { GtkListStore *store; GtkWidget *view; GtkTreeViewColumn *column; GtkTreeSelection *select; gchar tmpstr[32]; gint i, width, colnum; if (sheet->varlist != NULL) { sheet->datacols = 0; for (i=1; i<=sheet->varlist[0]; i++) { if (spreadsheet_hide(sheet->varlist[i], datainfo)) { gretl_list_delete_at_pos(sheet->varlist, i--); } else { sheet->datacols += 1; } } if (sheet->varlist[0] < datainfo->v - 1) { sheet->flags |= SHEET_SHORT_VARLIST; } } store = make_sheet_liststore(sheet); view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store)); g_object_unref(G_OBJECT(store)); gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(view), TRUE); /* build and attach the (two) cell renderers */ create_sheet_cell_renderers(sheet); /* construct the observation marker (first) column */ if (sheet->matrix == NULL) { width = get_obs_col_width(); } else { width = get_row_label_width(); } column = gtk_tree_view_column_new_with_attributes(NULL, sheet->dumbcell, "text", 0, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(view), column); set_up_sheet_column(column, width, FALSE); g_object_set_data(G_OBJECT(column), "colnum", GINT_TO_POINTER(0)); width = get_data_col_width(); if (sheet->matrix != NULL) { for (i=1; i<=sheet->datacols; i++) { sprintf(tmpstr, "%d", i); column = gtk_tree_view_column_new_with_attributes(tmpstr, sheet->datacell, "text", i, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(view), column); set_up_sheet_column(column, width, TRUE); g_object_set_data(G_OBJECT(column), "colnum", GINT_TO_POINTER(i)); } } else { colnum = 0; for (i=1; i<=sheet->varlist[0]; i++) { int vi = sheet->varlist[i]; if (spreadsheet_hide(vi, datainfo)) { continue; } colnum++; double_underscores(tmpstr, datainfo->varname[vi]); column = gtk_tree_view_column_new_with_attributes(tmpstr, sheet->datacell, "text", colnum, NULL); gtk_tree_view_append_column(GTK_TREE_VIEW(view), column); set_up_sheet_column(column, width, TRUE); g_object_set_data(G_OBJECT(column), "colnum", GINT_TO_POINTER(colnum)); } } /* set the selection property on the tree view */ select = gtk_tree_view_get_selection(GTK_TREE_VIEW(view)); gtk_tree_selection_set_mode(select, GTK_SELECTION_NONE); g_signal_connect(G_OBJECT(view), "cursor-changed", G_CALLBACK(update_cell_position), sheet); g_signal_connect(G_OBJECT(view), "key_press_event", G_CALLBACK(catch_spreadsheet_key), sheet); /* attach to sheet struct */ sheet->view = view; return 0; } static void free_spreadsheet (GtkWidget *widget, Spreadsheet **psheet) { Spreadsheet *sheet = *psheet; if (sheet->popup != NULL) { gtk_widget_destroy(sheet->popup); } free(sheet->varlist); if (sheet->matrix == NULL) { set_dataset_locked(FALSE); } if (sheet->matrix != NULL && sheet->oldmat != NULL) { /* delete the copied matrix */ gretl_matrix_free(sheet->matrix); } free(sheet); *psheet = NULL; } static void free_matrix_sheet (GtkWidget *widget, Spreadsheet *sheet) { if (sheet->popup != NULL) { gtk_widget_destroy(sheet->popup); } free(sheet->varlist); if (sheet->matrix == NULL) { set_dataset_locked(FALSE); } if (sheet->matrix != NULL && sheet->oldmat != NULL) { /* delete the copied matrix */ gretl_matrix_free(sheet->matrix); } free(sheet); } static int sheet_list_empty (Spreadsheet *sheet) { int ret = 0; if (sheet->varlist == NULL) { free(sheet); ret = 1; } else { int i, ns = 0; for (i=1; i<=sheet->varlist[0]; i++) { if (var_is_series(datainfo, sheet->varlist[i])) { ns++; } } if (ns == 0) { free(sheet->varlist); free(sheet); ret = 1; } } if (ret) { errbox(_("No series to edit")); } return ret; } static Spreadsheet *spreadsheet_new (SheetCmd c) { Spreadsheet *sheet; sheet = mymalloc(sizeof *sheet); if (sheet == NULL) return NULL; sheet->view = NULL; sheet->win = NULL; sheet->locator = NULL; #if 0 sheet->entry = NULL; #endif sheet->popup = NULL; sheet->ifac = NULL; sheet->save = NULL; sheet->dumbcell = NULL; sheet->datacell = NULL; sheet->datacols = sheet->datarows = 0; sheet->totcols = 0; sheet->added_vars = 0; sheet->orig_main_v = 0; sheet->orig_nobs = 0; sheet->modified = 0; sheet->cid = 0; sheet->varlist = NULL; sheet->matrix = NULL; sheet->oldmat = NULL; sheet->cmd = c; sheet->flags = 0; sheet->mname[0] = '\0'; if (c == SHEET_EDIT_MATRIX) { return sheet; } if (datainfo->t1 != 0 || datainfo->t2 < datainfo->n - 1) { sheet->flags |= SHEET_SUBSAMPLED; } sheet->orig_nobs = datainfo->t2 - datainfo->t1 + 1; if (sheet->cmd == SHEET_NEW_DATASET) { sheet->varlist = gretl_list_new(1); } else { if (sheet->cmd == SHEET_EDIT_VARLIST) { sheet->varlist = main_window_selection_as_list(); } else { sheet->varlist = full_var_list(datainfo, NULL); } if (sheet_list_empty(sheet)) { return NULL; } } if (sheet->varlist == NULL) { free(sheet); sheet = NULL; } else if (sheet->cmd == SHEET_NEW_DATASET) { sheet->varlist[1] = 1; } return sheet; } static void empty_dataset_guard (void) { int t, empty = 0; if (datainfo->v == 2) { empty = 1; for (t=0; tn; t++) { if (!na(Z[1][t])) { empty = 0; break; } } } if (empty) { data_status |= (GUI_DATA | MODIFIED_DATA); register_data(NULL, NULL, 0); infobox(_("Warning: there were missing observations")); } } static gint maybe_exit_sheet (GtkWidget *w, Spreadsheet *sheet) { int resp; if (sheet->modified) { resp = yes_no_dialog ("gretl", (sheet->matrix != NULL)? _("Save changes?") : _("Do you want to save changes you have\n" "made to the current data set?"), 1); if (resp == GRETL_YES) { get_data_from_sheet(NULL, sheet); } else if (resp == GRETL_CANCEL || resp == -1) { return FALSE; } } if (sheet->cmd == SHEET_NEW_DATASET) { empty_dataset_guard(); } gtk_widget_destroy(sheet->win); return FALSE; } static gint sheet_delete_event (GtkWidget *w, GdkEvent *event, Spreadsheet *sheet) { int resp; if (sheet->modified) { resp = yes_no_dialog ("gretl", (sheet->matrix != NULL)? _("Save changes?") : _("Do you want to save changes you have\n" "made to the current data set?"), 1); if (resp == GRETL_YES) { get_data_from_sheet(NULL, sheet); } else if (resp == GRETL_CANCEL) { return TRUE; } } if (sheet->cmd == SHEET_NEW_DATASET) { empty_dataset_guard(); } return FALSE; } static gint simple_exit_sheet (GtkWidget *w, Spreadsheet *sheet) { gtk_widget_destroy(sheet->win); return FALSE; } static void sheet_adjust_menu_state (Spreadsheet *sheet) { sheet->flags |= SHEET_ADD_OBS_OK | SHEET_INSERT_OBS_OK; if (complex_subsampled() || datainfo->t2 < datainfo->n - 1) { sheet->flags &= ~SHEET_ADD_OBS_OK; sheet->flags &= ~SHEET_INSERT_OBS_OK; disable_obs_menu(sheet->ifac); } else if ((sheet->flags & SHEET_SHORT_VARLIST) || dataset_is_panel(datainfo)) { sheet->flags &= ~SHEET_INSERT_OBS_OK; disable_insert_obs_item(sheet->ifac); } } static void size_matrix_window (Spreadsheet *sheet) { int nc = sheet->datacols; int w, h; if (nc < 2) { nc = 2; } w = get_row_label_width() + nc * get_data_col_width() + 30; if (w > 640) { w = 640; } h = sheet->datarows * 20 + 160; if (h > 480) { h = 480; } gtk_window_resize(GTK_WINDOW(sheet->win), w, h); } static void size_data_window (Spreadsheet *sheet) { int ocw = get_obs_col_width(); int dcw = get_data_col_width(); int extra = 40; int nc, w, h = 400; nc = (sheet->varlist[0] > 4)? 4 : sheet->varlist[0]; if (nc < 2) { extra += 40; } w = ocw + nc * dcw + extra; gtk_window_set_default_size(GTK_WINDOW(sheet->win), w, h); } static void real_show_spreadsheet (Spreadsheet **psheet, SheetCmd c, int block) { Spreadsheet *sheet = *psheet; GtkWidget *tmp, *button_box; GtkWidget *scroller, *main_vbox; GtkWidget *status_box, *mbar; int w, err = 0; sheet->win = gtk_window_new(GTK_WINDOW_TOPLEVEL); if (sheet->matrix != NULL) { if (sheet->mname[0] != '\0') { gchar *tmp = g_strdup_printf("gretl: %s", sheet->mname); gtk_window_set_title(GTK_WINDOW(sheet->win), tmp); g_free(tmp); } else { gtk_window_set_title(GTK_WINDOW(sheet->win), _("gretl: edit matrix")); } } else { gtk_window_set_title(GTK_WINDOW(sheet->win), _("gretl: edit data")); } if (sheet->matrix != NULL) { size_matrix_window(sheet); } else { size_data_window(sheet); } if (block) { g_signal_connect(G_OBJECT(sheet->win), "destroy", G_CALLBACK(gtk_main_quit), NULL); } g_signal_connect(G_OBJECT(sheet->win), "delete_event", G_CALLBACK(sheet_delete_event), sheet); main_vbox = gtk_vbox_new(FALSE, 5); gtk_container_set_border_width(GTK_CONTAINER(main_vbox), 5); gtk_container_add(GTK_CONTAINER(sheet->win), main_vbox); gtk_widget_show(main_vbox); /* add menu bar */ sheet->ifac = gtk_item_factory_new(GTK_TYPE_MENU_BAR, "
", NULL); #ifdef ENABLE_NLS gtk_item_factory_set_translate_func(sheet->ifac, menu_translate, NULL, NULL); #endif if (sheet->matrix == NULL) { gtk_item_factory_create_items(sheet->ifac, sizeof sheet_items / sizeof sheet_items[0], sheet_items, sheet); } else { gtk_item_factory_create_items(sheet->ifac, sizeof matrix_items / sizeof matrix_items[0], matrix_items, sheet); } mbar = gtk_item_factory_get_widget(sheet->ifac, "
"); gtk_box_pack_start(GTK_BOX(main_vbox), mbar, FALSE, FALSE, 0); gtk_widget_show(mbar); status_box = gtk_hbox_new(FALSE, 1); gtk_container_set_border_width(GTK_CONTAINER(status_box), 0); gtk_box_pack_start(GTK_BOX(main_vbox), status_box, FALSE, FALSE, 0); gtk_widget_show(status_box); sheet->locator = gtk_statusbar_new(); w = (sheet->matrix == NULL)? get_obs_col_width() : get_row_label_width(); gtk_widget_set_size_request(sheet->locator, 2 * w, 20); gtk_statusbar_set_has_resize_grip(GTK_STATUSBAR(sheet->locator), FALSE); sheet->cid = gtk_statusbar_get_context_id(GTK_STATUSBAR(sheet->locator), "current row and column"); gtk_box_pack_start(GTK_BOX(status_box), sheet->locator, FALSE, FALSE, 0); gtk_widget_show(sheet->locator); #if 0 /* not ready */ if (sheet->matrix != NULL) { sheet->entry = gtk_entry_new(); gtk_box_pack_start(GTK_BOX(status_box), sheet->entry, FALSE, FALSE, 0); gtk_widget_show(sheet->entry); } #endif scroller = gtk_scrolled_window_new(NULL, NULL); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroller), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroller), GTK_SHADOW_IN); build_sheet_view(sheet); gtk_container_add(GTK_CONTAINER(scroller), sheet->view); gtk_box_pack_start(GTK_BOX(main_vbox), scroller, TRUE, TRUE, TRUE); gtk_widget_show(sheet->view); gtk_widget_show(scroller); if (sheet->matrix == NULL) { sheet_adjust_menu_state(sheet); g_signal_connect(G_OBJECT(sheet->win), "destroy", G_CALLBACK(free_spreadsheet), psheet); } else { g_signal_connect(G_OBJECT(sheet->win), "destroy", G_CALLBACK(free_matrix_sheet), sheet); } /* control buttons */ button_box = gtk_hbutton_box_new(); gtk_button_box_set_layout(GTK_BUTTON_BOX(button_box), GTK_BUTTONBOX_END); gtk_button_box_set_spacing(GTK_BUTTON_BOX(button_box), 10); gtk_widget_show(button_box); gtk_box_pack_start(GTK_BOX(main_vbox), button_box, FALSE, FALSE, 0); gtk_container_set_border_width(GTK_CONTAINER(button_box), 0); if (sheet->matrix != NULL && sheet->oldmat == NULL) { tmp = gtk_button_new_from_stock(GTK_STOCK_OK); gtk_container_add(GTK_CONTAINER(button_box), tmp); g_signal_connect(G_OBJECT(tmp), "clicked", G_CALLBACK(get_data_from_sheet), sheet); g_signal_connect(G_OBJECT(tmp), "clicked", G_CALLBACK(simple_exit_sheet), sheet); gtk_widget_show(tmp); } else if (sheet->matrix != NULL) { tmp = gtk_button_new_from_stock(GTK_STOCK_SAVE_AS); gtk_container_add(GTK_CONTAINER(button_box), tmp); g_signal_connect(G_OBJECT(tmp), "clicked", G_CALLBACK(matrix_save_as), sheet); gtk_widget_show(tmp); tmp = gtk_button_new_from_stock(GTK_STOCK_SAVE); gtk_container_add(GTK_CONTAINER(button_box), tmp); g_signal_connect(G_OBJECT(tmp), "clicked", G_CALLBACK(get_data_from_sheet), sheet); gtk_widget_show(tmp); sheet->save = tmp; gtk_widget_set_sensitive(sheet->save, FALSE); tmp = gtk_button_new_from_stock(GTK_STOCK_CLOSE); gtk_container_add(GTK_CONTAINER(button_box), tmp); g_signal_connect(G_OBJECT(tmp), "clicked", G_CALLBACK(maybe_exit_sheet), sheet); gtk_widget_show(tmp); } else { tmp = gtk_button_new_from_stock(GTK_STOCK_APPLY); gtk_container_add(GTK_CONTAINER(button_box), tmp); g_signal_connect(G_OBJECT(tmp), "clicked", G_CALLBACK(get_data_from_sheet), sheet); gtk_widget_show(tmp); tmp = gtk_button_new_from_stock(GTK_STOCK_CLOSE); gtk_container_add(GTK_CONTAINER(button_box), tmp); g_signal_connect(G_OBJECT(tmp), "clicked", G_CALLBACK(maybe_exit_sheet), sheet); gtk_widget_show(tmp); } g_signal_connect(G_OBJECT(sheet->view), "button_press_event", G_CALLBACK(catch_spreadsheet_click), sheet); if (c == SHEET_EDIT_MATRIX) { err = add_matrix_data_to_sheet(sheet); } else { err = add_data_to_sheet(sheet, c); } if (err) { gtk_widget_destroy(sheet->win); return; } select_first_editable_cell(sheet); gtk_widget_show(sheet->win); if (c != SHEET_EDIT_MATRIX) { /* we can't have the user making confounding changes elsewhere, while editing the dataset here */ set_dataset_locked(TRUE); } if (block) { /* gretl_set_window_modal(sheet->win); */ gtk_main(); } } void show_spreadsheet (SheetCmd c) { static Spreadsheet *sheet; #ifdef G_OS_WIN32 if (datainfo->t2 - datainfo->t1 > 1600) { errbox(_("Sorry, can't edit more than 1600 rows")); return; } #endif if (datainfo->v == 1) { errbox(_("Please add a variable to the dataset first")); return; } if (sheet != NULL) { gdk_window_raise(sheet->win->window); return; } sheet = spreadsheet_new(c); if (sheet == NULL) { return; } real_show_spreadsheet(&sheet, c, 0); } struct gui_matrix_spec { char name[VNAMELEN]; int rows; int cols; double fill; char *formula; int uselist; gretl_matrix *m; }; struct mdialog { GtkWidget *dlg; GtkWidget *nentry; GtkWidget *ventry; GtkWidget *numerics; GtkWidget *formula; struct gui_matrix_spec *spec; }; static void spin_call (GtkWidget *s, int *n) { *n = (int) gtk_spin_button_get_value(GTK_SPIN_BUTTON(s)); } static void matrix_dialog_ok (GtkWidget *w, struct mdialog *mdlg) { const char *etxt; int err = 0; etxt = gtk_entry_get_text(GTK_ENTRY(mdlg->nentry)); if (etxt == NULL || *etxt == '\0') { infobox(_("You must give the matrix a name")); return; } if (get_matrix_by_name(etxt) != NULL && matrix_overwrite_check(etxt) == GRETL_NO) { return; } if ((err = check_varname(etxt))) { gui_errmsg(err); return; } strcpy(mdlg->spec->name, etxt); mdlg->spec->uselist = 0; if (GTK_WIDGET_SENSITIVE(mdlg->numerics)) { etxt = gtk_entry_get_text(GTK_ENTRY(mdlg->ventry)); if (check_atof(etxt)) { return; } mdlg->spec->fill = atof(etxt); } else if (GTK_WIDGET_SENSITIVE(mdlg->formula)) { etxt = gtk_entry_get_text(GTK_ENTRY(mdlg->formula)); if (etxt == NULL || *etxt == '\0') { errbox(_("The matrix formula is empty")); return; } mdlg->spec->formula = g_strdup(etxt); } else { /* will select data series */ mdlg->spec->uselist = 1; } gtk_widget_destroy(mdlg->dlg); } static gint choose_series (GtkWidget *w, struct mdialog *mdlg) { if (GTK_TOGGLE_BUTTON(w)->active) { if (mdlg->numerics != NULL) { gtk_widget_set_sensitive(mdlg->numerics, FALSE); } if (mdlg->formula != NULL) { gtk_widget_set_sensitive(mdlg->formula, FALSE); } } return FALSE; } static gint choose_numeric (GtkWidget *w, struct mdialog *mdlg) { if (GTK_TOGGLE_BUTTON(w)->active) { if (mdlg->numerics != NULL) { gtk_widget_set_sensitive(mdlg->numerics, TRUE); } if (mdlg->formula != NULL) { gtk_widget_set_sensitive(mdlg->formula, FALSE); } } return FALSE; } static gint choose_formula (GtkWidget *w, struct mdialog *mdlg) { if (GTK_TOGGLE_BUTTON(w)->active) { if (mdlg->numerics != NULL) { gtk_widget_set_sensitive(mdlg->numerics, FALSE); } if (mdlg->formula != NULL) { gtk_widget_set_sensitive(mdlg->formula, TRUE); } } return FALSE; } static int new_matrix_dialog (struct gui_matrix_spec *spec) { struct mdialog mdlg; GSList *group; GtkWidget *dlg; GtkWidget *hbox; GtkWidget *vbox; GtkWidget *tab; GtkWidget *rb; GtkWidget *w; int maxdim = 1000; int canceled = 0; dlg = gretl_dialog_new("Matrix", mdata->w, GRETL_DLG_BLOCK | GRETL_DLG_MODAL); vbox = GTK_DIALOG(dlg)->vbox; mdlg.dlg = dlg; mdlg.spec = spec; mdlg.numerics = NULL; mdlg.formula = NULL; /* top label */ hbox = gtk_hbox_new(FALSE, 5); w = gtk_label_new(_("New matrix")); gtk_box_pack_start(GTK_BOX(hbox), w, TRUE, TRUE, 0); gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 5); /* matrix name entry */ hbox = gtk_hbox_new(FALSE, 5); w = gtk_label_new(_("Name:")); gtk_box_pack_start(GTK_BOX(hbox), w, FALSE, FALSE, 5); w = gtk_entry_new(); gtk_entry_set_max_length(GTK_ENTRY(w), VNAMELEN-1); gtk_entry_set_width_chars(GTK_ENTRY(w), VNAMELEN+3); gtk_entry_set_activates_default(GTK_ENTRY(w), TRUE); gtk_box_pack_start(GTK_BOX(hbox), w, FALSE, FALSE, 5); gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 5); mdlg.nentry = w; /* matrix construction options */ /* option: build from series */ hbox = gtk_hbox_new(FALSE, 5); rb = gtk_radio_button_new_with_label(NULL, _("Build from series")); g_signal_connect(G_OBJECT(rb), "clicked", G_CALLBACK(choose_series), &mdlg); gtk_box_pack_start(GTK_BOX(hbox), rb, TRUE, TRUE, 0); gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 5); /* option: build numerically */ hbox = gtk_hbox_new(FALSE, 5); group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(rb)); rb = gtk_radio_button_new_with_label(group, _("Build numerically")); g_signal_connect(G_OBJECT(rb), "clicked", G_CALLBACK(choose_numeric), &mdlg); gtk_box_pack_start(GTK_BOX(hbox), rb, TRUE, TRUE, 0); gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 5); hbox = gtk_hbox_new(FALSE, 5); tab = gtk_table_new(3, 2, FALSE); gtk_table_set_col_spacing(GTK_TABLE(tab), 0, 5); w = gtk_label_new(_("Number of rows:")); gtk_misc_set_alignment(GTK_MISC(w), 0, 1); gtk_table_attach_defaults(GTK_TABLE(tab), w, 0, 1, 0, 1); w = gtk_spin_button_new_with_range(1, maxdim, 1); gtk_spin_button_set_value(GTK_SPIN_BUTTON(w), (gdouble) spec->rows); g_signal_connect(G_OBJECT(w), "value-changed", G_CALLBACK(spin_call), &spec->rows); gtk_table_attach_defaults(GTK_TABLE(tab), w, 1, 2, 0, 1); w = gtk_label_new(_("Number of columns:")); gtk_misc_set_alignment(GTK_MISC(w), 0, 1); gtk_table_attach_defaults(GTK_TABLE(tab), w, 0, 1, 1, 2); w = gtk_spin_button_new_with_range(1, maxdim, 1); gtk_spin_button_set_value(GTK_SPIN_BUTTON(w), (gdouble) spec->cols); g_signal_connect(G_OBJECT(w), "value-changed", G_CALLBACK(spin_call), &spec->cols); gtk_table_attach_defaults(GTK_TABLE(tab), w, 1, 2, 1, 2); w = gtk_label_new(_("Initial fill value:")); gtk_misc_set_alignment(GTK_MISC(w), 0, 1); gtk_table_attach_defaults(GTK_TABLE(tab), w, 0, 1, 2, 3); w = gtk_entry_new(); gtk_entry_set_max_length(GTK_ENTRY(w), VNAMELEN-1); gtk_entry_set_width_chars(GTK_ENTRY(w), VNAMELEN+3); gtk_entry_set_activates_default(GTK_ENTRY(w), TRUE); gtk_entry_set_text(GTK_ENTRY(w), "0"); gtk_table_attach_defaults(GTK_TABLE(tab), w, 1, 2, 2, 3); mdlg.ventry = w; w = gtk_label_new(" "); gtk_box_pack_start(GTK_BOX(hbox), w, FALSE, FALSE, 5); gtk_box_pack_start(GTK_BOX(hbox), tab, FALSE, FALSE, 5); gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 5); gtk_widget_set_sensitive(hbox, FALSE); mdlg.numerics = hbox; /* option: build from formula */ hbox = gtk_hbox_new(FALSE, 5); group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(rb)); rb = gtk_radio_button_new_with_label(group, _("Build from formula")); g_signal_connect(G_OBJECT(rb), "clicked", G_CALLBACK(choose_formula), &mdlg); gtk_box_pack_start(GTK_BOX(hbox), rb, TRUE, TRUE, 0); gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 5); hbox = gtk_hbox_new(FALSE, 5); w = gtk_label_new(" "); gtk_box_pack_start(GTK_BOX(hbox), w, FALSE, FALSE, 5); w = gtk_entry_new(); gtk_entry_set_max_length(GTK_ENTRY(w), MAXLEN); gtk_entry_set_width_chars(GTK_ENTRY(w), 32); gtk_entry_set_activates_default(GTK_ENTRY(w), TRUE); gtk_box_pack_start(GTK_BOX(hbox), w, FALSE, FALSE, 5); gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 5); gtk_widget_set_sensitive(w, FALSE); mdlg.formula = w; /* control buttons */ hbox = GTK_DIALOG(dlg)->action_area; w = cancel_delete_button(hbox, dlg, &canceled); w = ok_button(hbox); g_signal_connect(G_OBJECT(w), "clicked", G_CALLBACK(matrix_dialog_ok), &mdlg); gtk_widget_grab_default(w); gtk_widget_show_all(dlg); return canceled; } static int matrix_from_list (selector *sr) { struct gui_matrix_spec *s = selector_get_data(sr); const char *buf = selector_list(sr); int *list; int err = 0; if (buf == NULL || *buf == '\0') { errbox("No variables are selected"); return 1; } else { list = gretl_list_from_string(buf); } if (list == NULL) { nomem(); return 1; } s->m = gretl_matrix_data_subset_skip_missing(list, (const double **) Z, datainfo->t1, datainfo->t2, &err); if (!err) { err = add_or_replace_user_matrix(s->m, s->name); } if (err) { gui_errmsg(err); } free(list); return err; } static int matrix_from_formula (struct gui_matrix_spec *s) { gchar *genline = g_strdup_printf("matrix %s = %s", s->name, s->formula); int err; err = generate(genline, &Z, datainfo, OPT_NONE, NULL); if (err) { gui_errmsg(err); } else { s->m = get_matrix_by_name(s->name); if (s->m == NULL) { err = 1; } } g_free(genline); return err; } static int matrix_from_spec (struct gui_matrix_spec *s) { int err = 0; s->m = gretl_matrix_alloc(s->rows, s->cols); if (s->m == NULL) { err = E_ALLOC; } else { gretl_matrix_fill(s->m, s->fill); err = add_or_replace_user_matrix(s->m, s->name); } if (err) { gui_errmsg(err); } return err; } static void gui_matrix_spec_init (struct gui_matrix_spec *s) { s->name[0] = '\0'; s->rows = 2; s->cols = 2; s->fill = 0; s->formula = NULL; s->uselist = 1; s->m = NULL; } void gui_new_matrix (void) { Spreadsheet *sheet = NULL; struct gui_matrix_spec spec; int err = 0; gui_matrix_spec_init(&spec); err = new_matrix_dialog(&spec); if (err) { return; } if (spec.uselist) { /* matrix from listed vars */ simple_selection(_("Define matrix"), matrix_from_list, DEFINE_MATRIX, &spec); return; } else if (spec.formula != NULL) { /* matrix from genr-style formula */ matrix_from_formula(&spec); free(spec.formula); return; } else { /* numerical specification: open editor if OK */ err = matrix_from_spec(&spec); if (err) { return; } } sheet = spreadsheet_new(SHEET_EDIT_MATRIX); if (sheet == NULL) { return; } strcpy(sheet->mname, spec.name); sheet->matrix = spec.m; sheet->datarows = gretl_matrix_rows(sheet->matrix); sheet->datacols = gretl_matrix_cols(sheet->matrix); real_show_spreadsheet(&sheet, SHEET_EDIT_MATRIX, 1); } /* returns pointer to matrix-editing window */ static GtkWidget *edit_matrix (gretl_matrix *m, const char *name) { Spreadsheet *sheet = NULL; gretl_matrix *oldmat = m; int err = 0; if (m == NULL || name == NULL) { return NULL; } sheet = spreadsheet_new(SHEET_EDIT_MATRIX); if (sheet == NULL) { return NULL; } strcpy(sheet->mname, name); sheet->oldmat = oldmat; sheet->matrix = gretl_matrix_copy(oldmat); if (sheet->matrix == NULL) { err = E_ALLOC; } if (err) { nomem(); free(sheet); sheet = NULL; } else { sheet->datarows = gretl_matrix_rows(sheet->matrix); sheet->datacols = gretl_matrix_cols(sheet->matrix); real_show_spreadsheet(&sheet, SHEET_EDIT_MATRIX, 0); } return (sheet != NULL)? sheet->win : NULL; } void edit_user_matrix_by_name (const char *name) { user_matrix *u = get_user_matrix_by_name(name); gretl_matrix *m = get_matrix_by_name(name); GtkWidget *w; /* do we already have a window open, editing this matrix? */ w = match_window_by_data(u); if (w != NULL) { gtk_window_present(GTK_WINDOW(w)); return; } if (m == NULL) { errbox(_("Couldn't open '%s'"), name); } else { w = edit_matrix(m, name); if (w != NULL) { /* protect matrix from deletion while editing */ g_object_set_data(G_OBJECT(w), "object", u); g_signal_connect(G_OBJECT(w), "destroy", G_CALLBACK(winstack_remove), w); winstack_add(w); } } } /* mechanism for locking dataset against changes while editing */ static int locked; static void set_dataset_locked (gboolean s) { locked = s; flip(mdata->ifac, "/Sample", !s); } int dataset_locked (void) { if (locked) { errbox(_("You can't do this while editing the dataset")); } return locked; }