mirror of
https://github.com/eddyem/fitsview-hartmann.git
synced 2025-12-06 02:35:18 +03:00
531 lines
16 KiB
C
531 lines
16 KiB
C
// fitsheaders.c - functions to edit FITS head
|
|
//
|
|
// Copyright 2011 Edward V. Emelianoff <eddy@sao.ru>
|
|
//
|
|
// 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., 51 Franklin Street, Fifth Floor, Boston,
|
|
// MA 02110-1301, USA.
|
|
#include "fitsheaders.h"
|
|
#include "fits.h"
|
|
|
|
GtkListStore *combo_ = NULL;
|
|
GtkTreeSelection *sel;
|
|
GtkTreeView* Tree;
|
|
GtkWidget *del_button;
|
|
GtkWidget *window;
|
|
// Data types to write FITS keys into menu
|
|
gchar* key_types[] = {
|
|
"TSTRING", "TLOGICAL", "TBYTE", "TSHORT", "TUSHORT", "TINT", "TUINT",
|
|
"TLONG", "TULONG", "TLONGLONG", "TFLOAT", "TDOUBLE", "TCOMPLEX",
|
|
"TDBLCOMPLEX"
|
|
};
|
|
// Values itself
|
|
int type_vals[] = {
|
|
TSTRING,
|
|
TLOGICAL,
|
|
TBYTE,
|
|
TSHORT,
|
|
TUSHORT,
|
|
TINT,
|
|
TUINT,
|
|
TLONG,
|
|
TULONG,
|
|
TLONGLONG,
|
|
TFLOAT,
|
|
TDOUBLE,
|
|
TCOMPLEX,
|
|
TDBLCOMPLEX
|
|
};
|
|
|
|
int VALS_N; // Number of items
|
|
|
|
gchar *colnames[] = {N_("Type"), N_("Name"), N_("Value"), N_("Comment")};
|
|
#define VISIBLE_COLS 4 // The amount of visible columns
|
|
|
|
int find_list_type(char *type){
|
|
int i;
|
|
for(i = 0; i < VALS_N; i++)
|
|
if(strcmp(key_types[i], type) == 0) break;
|
|
if(i == VALS_N) // Error: there's no such value in list
|
|
i = 0;
|
|
return i;
|
|
}
|
|
|
|
gboolean ll_conv(gchar **value, int type){
|
|
long long ll_val, oll_val;
|
|
gboolean ret = TRUE;
|
|
ll_val = oll_val = strtoll(*value, NULL, 0);
|
|
//g_free(*value);
|
|
switch(type){
|
|
case TBYTE:
|
|
if(ll_val > CHAR_MAX || ll_val < CHAR_MIN) ll_val = 0L;
|
|
break;
|
|
case TSHORT:
|
|
if(ll_val > SHRT_MAX || ll_val < SHRT_MIN) ll_val = 0L;
|
|
break;
|
|
case TINT:
|
|
if(ll_val > INT_MAX || ll_val < INT_MIN) ll_val = 0L;
|
|
break;
|
|
case TLONG:
|
|
if(ll_val > LONG_MAX || ll_val < LONG_MIN) ll_val = 0L;
|
|
break;
|
|
case TLONGLONG:
|
|
if(ll_val > LLONG_MAX || ll_val < LLONG_MIN) ll_val = 0L;
|
|
break;
|
|
case TUSHORT:
|
|
if(ll_val > USHRT_MAX || ll_val < 0L) ll_val = 0L;
|
|
break;
|
|
case TUINT:
|
|
if(ll_val > UINT_MAX || ll_val < 0L) ll_val = 0L;
|
|
break;
|
|
case TULONG:
|
|
if((unsigned long)ll_val > ULONG_MAX || ll_val < 0L) ll_val = 0L;
|
|
break;
|
|
default:
|
|
ll_val = 0L;
|
|
break;
|
|
}
|
|
DBG("long long: %lld", ll_val);
|
|
if(ll_val != oll_val){
|
|
g_err(_("Error in value range!"));
|
|
ret = FALSE;
|
|
}
|
|
g_free(*value);
|
|
*value = g_strdup_printf("%lld", ll_val);
|
|
return ret;
|
|
}
|
|
|
|
gboolean mystrtod(gchar **val){
|
|
double d_val;
|
|
gboolean ret = TRUE;
|
|
char *eptr = NULL, *ptr = *val;
|
|
errno = 0;
|
|
d_val = strtod(ptr, &eptr);
|
|
DBG("double: %.16e (was: %s)", d_val, *val);
|
|
if((eptr && *eptr) || errno == ERANGE){
|
|
g_free(*val);
|
|
DBG("try convert %g into val", d_val);
|
|
*val = g_strdup_printf("%.16e", d_val);
|
|
g_err(_("Invalid double number!"));
|
|
ret = FALSE;
|
|
}
|
|
DBG("val: %s", *val);
|
|
return ret;
|
|
}
|
|
|
|
gboolean check_complex_number(gchar **val){
|
|
double re = 0., im = 0.;
|
|
gboolean ret = TRUE;
|
|
if(!cmplx_conv(*val, &re, &im)){
|
|
gchar *msg = g_strdup_printf("%s\n%s",
|
|
_("Format error: complex number must be in format"),
|
|
"RE + iIM, RE + jIM, RE iIM, RE jIM, (RE, IM) or (RE IM)");
|
|
g_err(msg);
|
|
g_free(msg);
|
|
ret = FALSE;
|
|
}
|
|
DBG("complex: (%g, %g)", re, im);
|
|
g_free(*val);
|
|
*val = g_strdup_printf("(%g, %g)", re, im);
|
|
return ret;
|
|
}
|
|
|
|
gboolean check_val(gchar **newval, int type){
|
|
gboolean b_val, ret = TRUE;
|
|
switch(type){
|
|
case TBYTE:
|
|
case TSHORT:
|
|
case TINT:
|
|
case TLONG:
|
|
case TLONGLONG:
|
|
case TUSHORT:
|
|
case TUINT:
|
|
case TULONG:
|
|
// convert *char -> long long vith checking value
|
|
ret = ll_conv(newval, type);
|
|
break;
|
|
case TFLOAT:
|
|
case TDOUBLE:
|
|
// convert *char -> double
|
|
ret = mystrtod(newval);
|
|
break;
|
|
case TCOMPLEX:
|
|
case TDBLCOMPLEX:
|
|
// check whether a number is complex
|
|
ret = check_complex_number(newval);
|
|
break;
|
|
case TLOGICAL:
|
|
// convert value into boolean
|
|
if(g_ascii_strncasecmp("true", *newval, 4)==0) b_val = TRUE;
|
|
else
|
|
b_val = atoi(*newval);
|
|
g_free(*newval);
|
|
*newval = g_strdup_printf("%s", b_val ? "TRUE" : "FALSE");
|
|
DBG("boolean: %s", *newval);
|
|
break;
|
|
default:
|
|
DBG("string: %s", *newval); // all other values a strings
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
gboolean try_change_val(IMAGE *ima, gchar **newval){
|
|
int type;
|
|
GtkTreeIter cur_items;
|
|
gboolean ret;
|
|
GtkTreeModel *model;
|
|
FNAME();
|
|
if(!gtk_tree_selection_get_selected(sel,&model, &cur_items)) return FALSE;
|
|
gtk_tree_model_get(GTK_TREE_MODEL(ima->store), &cur_items, L_TYPE, &type, -1);
|
|
ret = check_val(newval, type);
|
|
gtk_list_store_set(ima->store, &cur_items, L_VAL, *newval, -1);
|
|
DBG("ret");
|
|
return ret;
|
|
}
|
|
guint cur_column;
|
|
void edit_cell( GtkCellRendererText *cell,
|
|
gchar *path,
|
|
gchar *new_text,
|
|
IMAGE *ima){
|
|
GtkTreeModel *model;
|
|
GtkTreeIter cur_items;
|
|
cur_column = GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(cell), "column_num"));
|
|
gboolean nxtcol = TRUE, editable = TRUE;
|
|
if(!path) return;
|
|
DBG("path: %s, new: %s, colnum: %d", path, new_text, cur_column);
|
|
gchar *textptr;
|
|
if(!gtk_tree_selection_get_selected(sel,&model, &cur_items)) return;
|
|
switch(cur_column){
|
|
case L_TYPENM: // key type was changed
|
|
gtk_list_store_set(ima->store, &cur_items, L_TYPE,
|
|
type_vals[find_list_type(new_text)], -1);
|
|
gtk_list_store_set(ima->store, &cur_items, cur_column, new_text, -1);
|
|
break;
|
|
case L_VAL: // key value was changed
|
|
textptr = g_strdup_printf("%s", new_text);
|
|
nxtcol = try_change_val(ima, &textptr); // try to change parameter according to its type
|
|
g_free(textptr);
|
|
break;
|
|
default:
|
|
gtk_list_store_set(ima->store, &cur_items, cur_column, new_text, -1);
|
|
break;
|
|
}
|
|
if(nxtcol){
|
|
if(cur_column < L_COMM) // it's not a last column - select next
|
|
cur_column++;
|
|
else{
|
|
cur_column = 0;
|
|
editable = FALSE;
|
|
}
|
|
}
|
|
DBG("set cursor col:%d, e=%d", cur_column, editable);
|
|
gtk_tree_view_set_cursor(Tree,
|
|
gtk_tree_model_get_path(GTK_TREE_MODEL(ima->store), &cur_items),
|
|
gtk_tree_view_get_column(Tree, cur_column), editable);
|
|
DBG("ret");
|
|
}
|
|
/*
|
|
void cancel_edit(GtkCellRenderer *renderer){
|
|
cur_column = GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(renderer), "column_num"));
|
|
gtk_tree_view_set_cursor(Tree,
|
|
gtk_tree_model_get_path(GTK_TREE_MODEL(store), &cur_items),
|
|
gtk_tree_view_get_column(Tree, cur_column), FALSE);
|
|
}
|
|
*/
|
|
void add_key( IMAGE *ima,
|
|
gchar *keytype, gchar *keyname, gchar *keyval,
|
|
gchar *keycomment, int keyfitstype){
|
|
GtkTreeIter iter;
|
|
//~ FNAME();
|
|
gtk_list_store_append(ima->store, &iter);
|
|
gtk_list_store_set(ima->store, &iter,
|
|
L_TYPENM, keytype,
|
|
L_KEY, keyname,
|
|
L_VAL, keyval,
|
|
L_COMM, keycomment,
|
|
L_TYPE, keyfitstype,
|
|
-1);// the last element must be -1
|
|
}
|
|
|
|
void init_keylist(IMAGE *ima){
|
|
/* Create a list model.
|
|
* We will have 4 columns: name of key type, key name, key value, comment, type
|
|
*/
|
|
if(ima->store) g_object_unref(G_OBJECT(ima->store)); // clear list, if it already exists
|
|
ima->store = gtk_list_store_new(L_MAX, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT);
|
|
}
|
|
|
|
GtkWidget *create_treeview(IMAGE *ima){
|
|
int i, i_max = sizeof(key_types)/sizeof(char*);;
|
|
GtkTreeIter iter;
|
|
GtkCellRenderer *renderer;
|
|
GtkTreeViewColumn *column;
|
|
if(combo_)
|
|
gtk_list_store_clear(combo_);
|
|
combo_ = gtk_list_store_new(1, G_TYPE_STRING);
|
|
for(i = 0; i < i_max; i++){
|
|
gtk_list_store_append(combo_, &iter);
|
|
gtk_list_store_set(combo_, &iter, 0, key_types[i], -1);
|
|
}
|
|
// create list (treeview) according model store
|
|
Tree = (GtkTreeView*)gtk_tree_view_new_with_model(GTK_TREE_MODEL(ima->store));
|
|
// Create columns in list
|
|
for(i = 0; i < VISIBLE_COLS; i++){
|
|
if(i == 0){
|
|
renderer = gtk_cell_renderer_combo_new();
|
|
g_object_set(renderer, "text-column", 0,
|
|
"editable", TRUE, "has-entry", FALSE, "model",
|
|
GTK_TREE_MODEL(combo_), NULL);
|
|
}
|
|
else{
|
|
renderer = gtk_cell_renderer_text_new ();
|
|
g_object_set(renderer, "editable", TRUE, NULL);
|
|
}
|
|
g_object_set_data(G_OBJECT(renderer), "column_num", GINT_TO_POINTER(i));
|
|
column = gtk_tree_view_column_new_with_attributes(_(colnames[i]),
|
|
renderer, "text", i, NULL);
|
|
gtk_tree_view_append_column(Tree, column); // insert column
|
|
gtk_tree_view_column_set_sort_column_id(column, i);// it may be sorted
|
|
g_signal_connect(renderer, "edited", G_CALLBACK(edit_cell), ima);
|
|
// g_signal_connect(renderer, "editing-canceled", G_CALLBACK(cancel_edit), NULL);
|
|
}
|
|
/*
|
|
renderer = gtk_cell_renderer_text_new ();
|
|
column = gtk_tree_view_column_new_with_attributes ("hidden",renderer, NULL);
|
|
g_object_set(column, "visible", FALSE, NULL);
|
|
gtk_tree_view_append_column(Tree, column);
|
|
*/
|
|
return GTK_WIDGET(Tree);
|
|
}
|
|
|
|
static void list_changed(GtkTreeSelection *sel){
|
|
GtkTreeModel *model;
|
|
GtkTreeIter cur_items;
|
|
if(gtk_tree_selection_get_selected(sel,&model, &cur_items))
|
|
gtk_widget_set_sensitive(del_button, TRUE);
|
|
else
|
|
gtk_widget_set_sensitive(del_button, FALSE);
|
|
/*
|
|
GtkTreeModel *model;
|
|
FNAME();
|
|
gboolean showdel = FALSE;
|
|
if(!GTK_IS_TREE_SELECTION(sel)){
|
|
showdel = TRUE;
|
|
if(GTK_IS_TREE_SELECTION(obj)) DBG("!!!");
|
|
cur_column = GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(sel), "column_num"));
|
|
DBG("curcol: %d", cur_column);
|
|
}
|
|
else if(gtk_tree_selection_get_selected(sel,&model, &cur_items)){
|
|
gint column = L_VAL;
|
|
gchar *value;
|
|
gtk_tree_model_get(model, &cur_items, column, &value, -1);
|
|
DBG("selected: %s", value);
|
|
gtk_tree_view_set_cursor(Tree,
|
|
gtk_tree_model_get_path(model, &cur_items),
|
|
gtk_tree_view_get_column(Tree, cur_column), FALSE);
|
|
g_free(value);
|
|
showdel = TRUE;
|
|
}
|
|
gtk_widget_set_sensitive(del_button, showdel);
|
|
DBG("ret");*/
|
|
}
|
|
|
|
void newline(IMAGE *ima){
|
|
FNAME();
|
|
GtkTreeIter cur_items;
|
|
gtk_list_store_append(ima->store, &cur_items);
|
|
gtk_list_store_set(ima->store, &cur_items,
|
|
L_TYPENM, key_types[0],
|
|
L_KEY, "NEW_KEY",
|
|
L_VAL, "",
|
|
L_COMM, "",
|
|
L_TYPE, type_vals[0],
|
|
-1);
|
|
// Set cursor onto a first field
|
|
gtk_tree_view_set_cursor(Tree,
|
|
gtk_tree_model_get_path(GTK_TREE_MODEL(ima->store), &cur_items),
|
|
gtk_tree_view_get_column(Tree, 0), TRUE);
|
|
DBG("ret");
|
|
}
|
|
|
|
gboolean del_line(IMAGE *ima){
|
|
GtkTreeIter iter;
|
|
GtkTreeModel *model = GTK_TREE_MODEL(ima->store);
|
|
gboolean ret = FALSE;
|
|
FNAME();
|
|
GtkTreeSelection *selection = gtk_tree_view_get_selection(Tree);
|
|
GtkTreePath *path;
|
|
if(gtk_tree_selection_get_selected(selection, NULL, &iter)){
|
|
path = gtk_tree_model_get_path(model, &iter);
|
|
ret = gtk_list_store_remove(ima->store, &iter);
|
|
// check wherher &cur_items was last
|
|
if(gtk_list_store_iter_is_valid(ima->store, &iter)){
|
|
gtk_tree_path_free(path);
|
|
path = gtk_tree_model_get_path(model, &iter);
|
|
}
|
|
else gtk_tree_path_prev(path); // goto previous
|
|
if(path && gtk_tree_model_get_iter_first(model, &iter))
|
|
gtk_tree_view_set_cursor(Tree, path, NULL, FALSE);
|
|
else DBG("No entries");
|
|
gtk_tree_path_free(path);
|
|
}
|
|
DBG("ret");
|
|
return ret;
|
|
}
|
|
/*
|
|
void show_menu(){
|
|
GtkWidget *menu, *menuitem;
|
|
gchar *text;
|
|
int i;
|
|
menu = gtk_menu_new();
|
|
for(i = 0; i < 10; i++){
|
|
text = g_strdup_printf("%d", i);
|
|
menuitem = gtk_menu_item_new_with_label(text);
|
|
gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
|
|
gtk_widget_show(menuitem);
|
|
g_free(text);
|
|
}
|
|
gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, 3, 0);
|
|
}
|
|
|
|
gboolean treeview_button_press_cb(GtkWidget *wi, GdkEventButton *ev, gpointer user_data)
|
|
{
|
|
GtkTreeSelection *sel;
|
|
GtkTreeModel *model;
|
|
GtkTreePath *path, *oldpath;
|
|
GtkTreeViewColumn *column;
|
|
GtkTreeIter iter;
|
|
gchar *colname;
|
|
|
|
// if there's no path where the click occurred...
|
|
if(!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(wi), ev->x, ev->y, &path, &column, NULL, NULL))
|
|
return FALSE; // ...return FALSE to allow the event to continue
|
|
colname = strdup(gtk_tree_view_column_get_title(column));
|
|
// get the tree view's selection
|
|
sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(wi));
|
|
|
|
switch(ev->button)
|
|
{
|
|
// LMB
|
|
case 1:
|
|
// check to see if this is already the selected path
|
|
if(gtk_tree_selection_get_selected(sel, &model, &iter))
|
|
{
|
|
oldpath = gtk_tree_model_get_path(model, &iter);
|
|
if(gtk_tree_path_compare(oldpath, path) == 0)
|
|
{
|
|
gtk_tree_path_free(oldpath);
|
|
gtk_tree_path_free(path);
|
|
return FALSE;
|
|
}
|
|
gtk_tree_path_free(oldpath);
|
|
}
|
|
|
|
// select the clicked path
|
|
gtk_tree_selection_select_path(sel, path);
|
|
return TRUE;
|
|
// RMB
|
|
case 3:
|
|
if(strncmp(colname, "No.", 3)){
|
|
gtk_tree_path_free(path);
|
|
return TRUE;
|
|
}
|
|
show_menu();
|
|
// gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, 3, ev->time);
|
|
break;
|
|
}
|
|
|
|
|
|
// free our path
|
|
gtk_tree_path_free(path);
|
|
|
|
return FALSE; // allow it to continue
|
|
}
|
|
*/
|
|
|
|
void close_dlg(){
|
|
gtk_widget_destroy(window);
|
|
}
|
|
|
|
static gboolean press_key(IMAGE *ima, GdkEventKey * event){
|
|
gboolean ret = FALSE;
|
|
if(event->state & (GDK_CONTROL_MASK)){
|
|
switch(event->keyval){
|
|
// ctrl+n inserts a line
|
|
case 'n': case 'N':
|
|
newline(ima);
|
|
ret = TRUE;
|
|
break;
|
|
// ctrl+d deletes current line
|
|
case 'd': case 'D':
|
|
ret = del_line(ima);
|
|
break;
|
|
// ctrl+w closes the window
|
|
case 'w': case 'W':
|
|
close_dlg();
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void edit_headers(Window *parent){
|
|
VALS_N = sizeof(key_types) / sizeof(gchar *);
|
|
GtkWidget *treeview, *vbox, *hbox, *button;
|
|
if(!parent->image || !parent->image->store){
|
|
g_err(_("Open fits file first"));
|
|
return;
|
|
}
|
|
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
|
|
gtk_window_set_title(GTK_WINDOW(window), "List");
|
|
gtk_signal_connect(GTK_OBJECT(window),"delete_event",GTK_SIGNAL_FUNC(gtk_false),NULL);
|
|
gtk_signal_connect(GTK_OBJECT(window),"destroy",GTK_SIGNAL_FUNC(close_dlg), NULL);
|
|
treeview = create_treeview(parent->image);
|
|
sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
|
|
g_signal_connect(G_OBJECT(sel), "changed", G_CALLBACK(list_changed), NULL);
|
|
//g_signal_connect(G_OBJECT(treeview), "cursor-changed", G_CALLBACK(list_changed), NULL);
|
|
|
|
vbox = gtk_vbox_new(FALSE, 5);
|
|
gtk_container_add (GTK_CONTAINER (window), vbox);
|
|
|
|
GtkWidget *sw = gtk_scrolled_window_new (NULL, NULL);
|
|
gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw), GTK_SHADOW_ETCHED_IN);
|
|
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), GTK_POLICY_AUTOMATIC,
|
|
GTK_POLICY_AUTOMATIC);
|
|
gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE, 0);
|
|
gtk_container_add(GTK_CONTAINER(sw), treeview);
|
|
gtk_window_set_default_size(GTK_WINDOW (window), 640, 480);
|
|
|
|
//~ gtk_box_pack_start(GTK_BOX(vbox), treeview, TRUE, TRUE, 0);
|
|
|
|
hbox = gtk_hbox_new(FALSE, 5);
|
|
gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
|
|
button = gtk_button_new_with_label(_("New entry (ctrl+n)"));
|
|
g_signal_connect_swapped(G_OBJECT(button), "clicked",
|
|
G_CALLBACK(newline), parent->image);
|
|
gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, FALSE, 0);
|
|
|
|
del_button = gtk_button_new_with_label(_("Delete entry (ctrl+d)"));
|
|
g_signal_connect_swapped(G_OBJECT(del_button), "clicked",
|
|
G_CALLBACK(del_line), parent->image);
|
|
gtk_box_pack_end(GTK_BOX(hbox), del_button, FALSE, FALSE, 0);
|
|
gtk_widget_set_sensitive(del_button, FALSE);
|
|
|
|
button = gtk_button_new_with_label(_("Close (ctrl+w)"));
|
|
g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(close_dlg), NULL);
|
|
gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, FALSE, 0);
|
|
g_signal_connect_swapped(G_OBJECT(window), "key-press-event",
|
|
G_CALLBACK(press_key), parent->image);
|
|
run_modal_window(GTK_WINDOW(window), parent);
|
|
}
|