/*
* 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 <errno.h>
#include <ctype.h>
#include <float.h>
#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, "<Branch>", 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, "<Branch>", GNULL },
{ N_("/Variable/_Add"), NULL, sheet_add_var_callback, 0, NULL, GNULL }
};
static GtkItemFactoryEntry matrix_items[] = {
{ N_("/_Fill"), NULL, NULL, 0, "<Branch>", 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, "<Branch>", GNULL },
{ N_("/Properties/_View"), NULL, matrix_props_callback,
0, NULL, GNULL },
{ N_("/_Transform"), NULL, NULL, 0, "<Branch>", 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; i<n; i++) {
gtk_list_store_append(store, &iter);
}
} else if (sheet->point == 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; j<n; j++) {
gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter);
get_full_obs_string(rowlabel, sheet->datarows + 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; j<n; j++) {
gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter);
for (i=1; i<=sheet->datacols; 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; i<sheet->datarows; 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; i<sheet->totcols; 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; row<sheet->datarows; row++) {
gchar *str;
int col;
gtk_list_store_append(new_store, &new_iter);
for (col=0; col<sheet->totcols; 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; i<sheet->datarows; i++) {
for (j=0; j<sheet->datacols; 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; i<newvars; i++) {
newname = NULL;
vi = oldv + i;
gretl_list_append_term(&sheet->varlist, 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; s<sheet->datarows; 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; s<sheet->datarows; 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; i<n; i++) {
sprintf(rstr, "%d", sheet->datacols - n + i + 1);
add_treeview_column_with_title(sheet, rstr);
}
} else {
n = -n;
for (i=0; i<n; i++) {
column = gtk_tree_view_get_column(GTK_TREE_VIEW(sheet->view),
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; i<sheet->matrix->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; j<sheet->datacols; 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; i<sheet->datarows; 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; i<sheet->datarows; 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; i<sheet->datarows; i++) {
for (j=0; j<sheet->datacols; 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; t<datainfo->n; 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, "<main>",
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, "<main>");
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;
}
syntax highlighted by Code2HTML, v. 0.9.1