#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <locale.h>
#include <langinfo.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>

#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <Xm/Xm.h>
#include <Xm/Primitive.h>
#include <Xm/Label.h>
#include <Xm/CascadeB.h>
#include <Xm/PushB.h>
#include <Xm/Separator.h>
#include <Xm/RowColumn.h>
#include <Xm/ScrolledW.h>
#include <Xm/Protocols.h>
#include <Xm/List.h>
#include <Xm/Form.h>
#include <Xm/MessageB.h>
#include <Xm/SelectioB.h>
#include <Xm/Scale.h>
#include <Xm/Text.h>
#include <Xm/FileSB.h>
#include <Xm/ToggleB.h>
#include <Xm/DrawingA.h>
#include <Xm/Transfer.h>
#include <Xm/TransferP.h>

#include "RegEdit.h"
#include "ida.h"
#include "x11.h"
#include "man.h"
#include "readers.h"
#include "writers.h"
#include "viewer.h"
#include "op.h"
#include "lut.h"
#include "filter.h"
#include "color.h"
#include "icons.h"
#include "browser.h"
#include "filelist.h"
#include "xdnd.h"
#include "selections.h"
#include "sane.h"
#include "curl.h"
#include "idaconfig.h"

/* ---------------------------------------------------------------------- */

static void popup_ac(Widget, XEvent*, String*, Cardinal*);
static void exit_ac(Widget, XEvent*, String*, Cardinal*);
static void next_ac(Widget, XEvent*, String*, Cardinal*);
static void prev_ac(Widget, XEvent*, String*, Cardinal*);
static void next_page_ac(Widget, XEvent*, String*, Cardinal*);
static void prev_page_ac(Widget, XEvent*, String*, Cardinal*);
static void zoom_ac(Widget, XEvent*, String*, Cardinal*);
static void scroll_ac(Widget, XEvent*, String*, Cardinal*);
static void debug_ac(Widget, XEvent*, String*, Cardinal*);
static void load_ac(Widget, XEvent*, String*, Cardinal*);
static void save_ac(Widget, XEvent*, String*, Cardinal*);
static void scan_ac(Widget, XEvent*, String*, Cardinal*);
static void print_ac(Widget, XEvent*, String*, Cardinal*);

static void undo_ac(Widget, XEvent*, String*, Cardinal*);
static void filter_ac(Widget, XEvent*, String*, Cardinal*);
static void gamma_ac(Widget, XEvent*, String*, Cardinal*);
static void bright_ac(Widget, XEvent*, String*, Cardinal*);
static void contrast_ac(Widget, XEvent*, String*, Cardinal*);
static void color_ac(Widget, XEvent*, String*, Cardinal*);
static void f3x3_ac(Widget, XEvent*, String*, Cardinal*);
static void resize_ac(Widget, XEvent*, String*, Cardinal*);
static void rotate_ac(Widget, XEvent*, String*, Cardinal*);
static void sharpe_ac(Widget, XEvent*, String*, Cardinal*);

static XtActionsRec actionTable[] = {
    { "Exit",     exit_ac      },
    { "Next",     next_ac      },
    { "Prev",     prev_ac      },
    { "NextPage", next_page_ac },
    { "PrevPage", prev_page_ac },
    { "Zoom",     zoom_ac      },
    { "Scroll",   scroll_ac    },
    { "Debug",    debug_ac     },
    { "Popup",    popup_ac     },
    { "Man",      man_action   },
    { "Load",     load_ac      },
    { "Save",     save_ac      },
    { "Scan",     scan_ac      },
    { "Print",    print_ac     },
    { "Browser",  browser_ac   },
    { "Filelist", filelist_ac  },

    { "Undo",     undo_ac      },
    { "Filter",   filter_ac    },
    { "Gamma",    gamma_ac     },
    { "Bright",   bright_ac    },
    { "Contrast", contrast_ac  },
    { "Color",    color_ac     },
    { "F3x3",     f3x3_ac      },
    { "Resize",   resize_ac    },
    { "Rotate",   rotate_ac    },
    { "Sharpe",   sharpe_ac    },

    { "Ipc",      ipc_ac       },
    { "Xdnd",     XdndAction   },
};

/* ---------------------------------------------------------------------- */

XtAppContext       app_context;
Display            *dpy;
Widget             app_shell;
int                gray=0;
char               *binary;
struct ida_viewer  *ida;

/* ---------------------------------------------------------------------- */

struct ARGS args;
unsigned int pcd_res;
unsigned int sane_res;

XtResource args_desc[] = {
    {
	"debug",
	XtCBoolean, XtRBoolean, sizeof(Boolean),
	XtOffset(struct ARGS*,debug),
	XtRString, "false"
    },{
	"help",
	XtCBoolean, XtRBoolean, sizeof(Boolean),
	XtOffset(struct ARGS*,help),
	XtRString, "false"
    },{
	"testload",
	XtCBoolean, XtRBoolean, sizeof(Boolean),
	XtOffset(struct ARGS*,testload),
	XtRString, "false"
    }
};
const int args_count = XtNumber(args_desc);

XrmOptionDescRec opt_desc[] = {
    { "-d",          "debug",       XrmoptionNoArg,  "true" },
    { "-debug",      "debug",       XrmoptionNoArg,  "true" },
    { "-testload",   "testload",    XrmoptionNoArg,  "true" },
    { "-h",          "help",        XrmoptionNoArg,  "true" },
    { "-help",       "help",        XrmoptionNoArg,  "true" },
    { "--help",      "help",        XrmoptionNoArg,  "true" },
};
const int opt_count = (sizeof(opt_desc)/sizeof(XrmOptionDescRec));

static String fallback_ressources[] = {
#include "Ida.ad.h"
    NULL
};

/* ---------------------------------------------------------------------- */

static struct ida_writer *cwriter;
static char *save_filename;
static char *print_command;

static Widget control_shell,status;
static Atom   wm_delete_window;

static Widget view,loadbox,savebox,printbox;

/* file list */
static Widget wlist;
static char **files = NULL;
static int  cfile   = -1;
static int  nfiles  = 0;
static int  cpage   = 0;
static int  npages  = 1;

/* filter controls */
static int gamma_val    = 100;
static int bright_val   = 0;
static int contrast_val = 0;
static int rotate_val   = 0;
static int sharpe_val   = 10;

static struct MY_TOPLEVELS {
    char        *name;
    Widget      *shell;
    int         mapped;
} my_toplevels [] = {
    { "control",   &control_shell },
};
#define TOPLEVELS (sizeof(my_toplevels)/sizeof(struct MY_TOPLEVELS))

/* ---------------------------------------------------------------------- */

static void
popup_ac(Widget widget, XEvent *event,
	 String *params, Cardinal *num_params)
{
    unsigned int i;

    /* which window we are talking about ? */
    if (*num_params > 0) {
	for (i = 0; i < TOPLEVELS; i++) {
	    if (0 == strcasecmp(my_toplevels[i].name,params[0]))
		break;
	}
	if (i == TOPLEVELS) {
	    fprintf(stderr,"PopupAction: oops: shell not found (name=%s)\n",
		    params[0]);
	    return;
	}
    } else {
	for (i = 0; i < TOPLEVELS; i++) {
	    if (*(my_toplevels[i].shell) == widget)
		break;
	}
	if (i == TOPLEVELS) {
	    fprintf(stderr,"PopupAction: oops: shell not found (%p:%s)\n",
		    widget,XtName(widget));
	    return;
	}
    }

    /* popup/down window */
    if (!my_toplevels[i].mapped) {
	XtPopup(*(my_toplevels[i].shell), XtGrabNone);
	my_toplevels[i].mapped = 1;
    } else {
	XtPopdown(*(my_toplevels[i].shell));
	my_toplevels[i].mapped = 0;
    }
}

static void
popupdown_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
{
    int i = 0;
    popup_ac(clientdata, NULL, NULL, &i);
}

/* ---------------------------------------------------------------------- */

void
destroy_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
{
    XtDestroyWidget(clientdata);
}

void
action_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
{
    char *calls, *action, *argv[16]; /* max: F3x3(9 args) */
    int argc;

    calls = strdup(clientdata);
    action = strtok(calls,"(),");
    for (argc = 0; NULL != (argv[argc] = strtok(NULL,"(),")); argc++)
	/* nothing */;
    XtCallActionProc(widget,action,NULL,argv,argc);
    free(calls);
}

static void
about_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
{
    Widget msgbox;
    
    msgbox = XmCreateInformationDialog(app_shell,"aboutbox",NULL,0);
    XtUnmanageChild(XmMessageBoxGetChild(msgbox,XmDIALOG_HELP_BUTTON));
    XtUnmanageChild(XmMessageBoxGetChild(msgbox,XmDIALOG_CANCEL_BUTTON));
    XtAddCallback(msgbox,XmNokCallback,destroy_cb,msgbox);
    XtManageChild(msgbox);
}

#if 0
static void
sorry_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
{
    Widget msgbox;
    
    msgbox = XmCreateErrorDialog(app_shell,"sorrybox",NULL,0);
    XtUnmanageChild(XmMessageBoxGetChild(msgbox,XmDIALOG_HELP_BUTTON));
    XtUnmanageChild(XmMessageBoxGetChild(msgbox,XmDIALOG_CANCEL_BUTTON));
    XtAddCallback(msgbox,XmNokCallback,destroy_cb,msgbox);
    XtManageChild(msgbox);
}
#endif

void
debug_ac(Widget widget, XEvent *event, String *params, Cardinal *num)
{
    unsigned int i;

    fprintf(stderr,"Debug:");
    for (i = 0; i < *num; i++)
	fprintf(stderr," %s",params[i]);
    fprintf(stderr,"\n");
}

static void
display_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
{
    XmDisplayCallbackStruct *arg = call_data;
    
    switch (arg->reason) {
    case XmCR_NO_RENDITION:
	fprintf(stderr,"display_cb: no rendition: \"%s\"\n",arg->tag);
	break;
    case XmCR_NO_FONT:
	fprintf(stderr,"display_cb: no font: \"%s\"\n",arg->font_name);
	break;
    default:
	/* should not happen */
	fprintf(stderr,"display_cb: unknown reason [%d]\n",arg->reason);
	break;
    }
}

/* ---------------------------------------------------------------------- */

struct ptr_list {
    struct ptr_list *next;
    Widget widget;
};
struct ptr_list *ptr_head;

void ptr_register(Widget widget)
{
    struct ptr_list *item;

    if (XtWindow(widget))
	XDefineCursor(XtDisplay(widget), XtWindow(widget),
		      ptrs[POINTER_NORMAL]);
    item = malloc(sizeof(*item));
    memset(item,0,sizeof(*item));
    item->widget = widget;
    item->next = ptr_head;
    ptr_head = item;
}

void ptr_unregister(Widget widget)
{
    struct ptr_list *item,*fitem;

    if (ptr_head->widget == widget) {
	fitem = ptr_head;
	ptr_head = ptr_head->next;
	free(fitem);
	return;
    }
    for (item = ptr_head; NULL != item->next; item = item->next) {
	if (item->next->widget == widget) {
	    fitem = item->next;
	    item->next = fitem->next;
	    free(fitem);
	    return;
	}
    }
    /* shouldn't happen */
    fprintf(stderr,"Oops: widget not found in list\n");
}

void ptr_busy(void)
{
    struct ptr_list *item;

    for (item = ptr_head; NULL != item; item = item->next) {
	if (!XtWindow(item->widget))
	    continue;
	XDefineCursor(XtDisplay(item->widget), XtWindow(item->widget),
		      ptrs[POINTER_BUSY]);
    }
    XSync(dpy,False);
}

void ptr_idle(void)
{
    struct ptr_list *item;

    for (item = ptr_head; NULL != item; item = item->next) {
	if (!XtWindow(item->widget))
	    continue;
	XDefineCursor(XtDisplay(item->widget), XtWindow(item->widget),
		      ptrs[POINTER_NORMAL]);
    }
}

/* ---------------------------------------------------------------------- */

static Boolean
exit_wp(XtPointer client_data)
{
    exit(0);
}

static void
exit_cb(Widget widget, XtPointer client_data, XtPointer calldata)
{
    XtAppAddWorkProc(app_context,exit_wp, NULL);
    XtDestroyWidget(app_shell);
}

void
exit_ac(Widget widget, XEvent *event, String *params, Cardinal *num)
{
    exit_cb(widget,NULL,NULL);
}

/* ---------------------------------------------------------------------- */

static void
list_update(void)
{
    XmStringTable tab;
    int i;

    tab = malloc(nfiles * sizeof(XmString));
    for (i = 0; i < nfiles; i++)
	tab[i] = XmStringGenerate(files[i], NULL, XmMULTIBYTE_TEXT, NULL);
    XtVaSetValues(wlist,
		  XmNitems, tab,
		  XmNitemCount, nfiles,
		  NULL);
    for (i = 0; i < nfiles; i++)
	XmStringFree(tab[i]);
    free(tab);
}

static int
list_append(char *filename)
{
    int i;

    for (i = 0; i < nfiles; i++)
	if (0 == strcmp(files[i],filename))
	    return i;
    files = realloc(files,sizeof(char*)*(nfiles+1));
    files[nfiles] = strdup(filename);
    nfiles++;
    return nfiles-1;
}

static void
list_cb(Widget widget, XtPointer client_data, XtPointer calldata)
{
    XmListCallbackStruct *list = calldata;

    if (0 == list->selected_item_count)
	return;
    cfile = list->selected_item_positions[0]-1;
    cpage = 0;
    npages = viewer_loadimage(ida,files[cfile],cpage);
    if (-1 == npages)
	return;
    resize_shell();
}

static void
pcd_set(Widget widget)
{
    WidgetList items;
    Cardinal nitems;
    unsigned int i;
    int value;

    value = GET_PHOTOCD_RES();
    XtVaGetValues(widget,XtNchildren,&items,
		  XtNnumChildren,&nitems,NULL);
    for (i = 0; i < nitems; i++)
	XmToggleButtonSetState(items[i],value == i+1,False);
    pcd_res      = value;
}

static void
pcd_cb(Widget widget, XtPointer client_data, XtPointer calldata)
{
    cfg_set_int(O_PHOTOCD_RES,(intptr_t)client_data);
    pcd_set(XtParent(widget));
}

static void
cfg_bool_cb(Widget widget, XtPointer client_data, XtPointer calldata)
{
    char *option = XtName(widget);
    Boolean value = XmToggleButtonGetState(widget);
    cfg_set_bool(O_OPTIONS, option, value);
}

static void
cfg_save_cb(Widget widget, XtPointer client_data, XtPointer calldata)
{
    ida_write_config();
}

static void
create_control(void)
{
    Widget form,menubar,tool,menu,smenu,push;

    control_shell = XtVaAppCreateShell("ctrl","Iv",
				       topLevelShellWidgetClass,
				       dpy,
				       XtNclientLeader,app_shell,
				       XmNdeleteResponse,XmDO_NOTHING,
				       NULL);
    XmdRegisterEditres(control_shell);
    XmAddWMProtocolCallback(control_shell,wm_delete_window,
			    popupdown_cb,control_shell);

    /* widgets */
    form = XtVaCreateManagedWidget("form", xmFormWidgetClass, control_shell,
				   NULL);
    menubar = XmCreateMenuBar(form,"bar",NULL,0);
    XtManageChild(menubar);
    tool = XtVaCreateManagedWidget("tool",xmRowColumnWidgetClass, form,
				   NULL);
    status = XtVaCreateManagedWidget("status", xmLabelWidgetClass, form,
				     NULL);
    wlist = XmCreateScrolledList(form,"list",NULL,0);
    XtManageChild(wlist);
    XtAddCallback(wlist,XmNdefaultActionCallback,list_cb,NULL);
    XtAddCallback(wlist,XmNdestinationCallback,selection_dest,NULL);
    dnd_add(wlist);
    
    /* menu - file */
    menu = XmCreatePulldownMenu(menubar,"fileM",NULL,0);
    XtVaCreateManagedWidget("file",xmCascadeButtonWidgetClass,menubar,
			    XmNsubMenuId,menu,NULL);
    push = XtVaCreateManagedWidget("load",xmPushButtonWidgetClass,menu,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,"Load()");
    push = XtVaCreateManagedWidget("save",xmPushButtonWidgetClass,menu,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,"Save()");
    push = XtVaCreateManagedWidget("browse",xmPushButtonWidgetClass,menu,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,"Browser()");
    push = XtVaCreateManagedWidget("filelist",xmPushButtonWidgetClass,menu,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,"Filelist()");
    XtVaCreateManagedWidget("sep",xmSeparatorWidgetClass,menu,NULL);
#ifdef HAVE_LIBSANE
    sane_menu(menu);
#endif
    push = XtVaCreateManagedWidget("print",xmPushButtonWidgetClass,menu,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,"Print()");
    XtVaCreateManagedWidget("sep",xmSeparatorWidgetClass,menu,NULL);
    push = XtVaCreateManagedWidget("quit",xmPushButtonWidgetClass,menu,NULL);
    XtAddCallback(push,XmNactivateCallback,exit_cb,NULL);

    /* menu - edit */
    menu = XmCreatePulldownMenu(menubar,"editM",NULL,0);
    XtVaCreateManagedWidget("edit",xmCascadeButtonWidgetClass,menubar,
			    XmNsubMenuId,menu,NULL);
    push = XtVaCreateManagedWidget("undo",xmPushButtonWidgetClass,menu,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,"Undo()");
    XtVaCreateManagedWidget("sep",xmSeparatorWidgetClass,menu,NULL);
    push = XtVaCreateManagedWidget("copy",xmPushButtonWidgetClass,menu,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,"Ipc(copy)");
    push = XtVaCreateManagedWidget("paste",xmPushButtonWidgetClass,menu,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,"Ipc(paste)");
    XtVaCreateManagedWidget("sep",xmSeparatorWidgetClass,menu,NULL);
    push = XtVaCreateManagedWidget("flipv",xmPushButtonWidgetClass,menu,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,"Filter(flip-vert)");
    push = XtVaCreateManagedWidget("fliph",xmPushButtonWidgetClass,menu,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,"Filter(flip-horz)");
    push = XtVaCreateManagedWidget("rotcw",xmPushButtonWidgetClass,menu,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,"Filter(rotate-cw)");
    push = XtVaCreateManagedWidget("rotccw",xmPushButtonWidgetClass,menu,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,"Filter(rotate-ccw)");
    push = XtVaCreateManagedWidget("invert",xmPushButtonWidgetClass,menu,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,"Filter(invert)");
    XtVaCreateManagedWidget("sep",xmSeparatorWidgetClass,menu,NULL);
    push = XtVaCreateManagedWidget("crop",xmPushButtonWidgetClass,menu,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,"Filter(crop)");
    push = XtVaCreateManagedWidget("acrop",xmPushButtonWidgetClass,menu,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,"Filter(autocrop)");
    push = XtVaCreateManagedWidget("scale",xmPushButtonWidgetClass,menu,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,"Resize()");
    push = XtVaCreateManagedWidget("rotany",xmPushButtonWidgetClass,menu,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,"Rotate()");

    /* menu - filters / operations */
    menu = XmCreatePulldownMenu(menubar,"opM",NULL,0);
    XtVaCreateManagedWidget("op",xmCascadeButtonWidgetClass,menubar,
			    XmNsubMenuId,menu,NULL);
    push = XtVaCreateManagedWidget("gamma",xmPushButtonWidgetClass,menu,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,"Gamma()");
    push = XtVaCreateManagedWidget("bright",xmPushButtonWidgetClass,menu,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,"Bright()");
    push = XtVaCreateManagedWidget("contr",xmPushButtonWidgetClass,menu,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,"Contrast()");
    push = XtVaCreateManagedWidget("color",xmPushButtonWidgetClass,menu,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,"Color()");
    push = XtVaCreateManagedWidget("gray",xmPushButtonWidgetClass,menu,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,"Filter(grayscale)");
    XtVaCreateManagedWidget("sep",xmSeparatorWidgetClass,menu,NULL);
    push = XtVaCreateManagedWidget("blur",xmPushButtonWidgetClass,menu,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,
		  "F3x3(1,1,1, 1,1,1, 1,1,1, 1,9,0)");
    push = XtVaCreateManagedWidget("sharpe",xmPushButtonWidgetClass,menu,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,"Sharpe()");
    push = XtVaCreateManagedWidget("edge",xmPushButtonWidgetClass,menu,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,
		  "F3x3(-1,-1,-1, -1,8,-1, -1,-1,-1)");
    push = XtVaCreateManagedWidget("emboss",xmPushButtonWidgetClass,menu,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,
		  "F3x3(1,0,0, 0,0,0, 0,0,-1, 0,0,128)");

    /* menu - view */
    menu = XmCreatePulldownMenu(menubar,"viewM",NULL,0);
    XtVaCreateManagedWidget("view",xmCascadeButtonWidgetClass,menubar,
			    XmNsubMenuId,menu,NULL);
    push = XtVaCreateManagedWidget("prev",xmPushButtonWidgetClass,menu,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,"Prev()");
    push = XtVaCreateManagedWidget("next",xmPushButtonWidgetClass,menu,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,"Next()");
    XtVaCreateManagedWidget("sep",xmSeparatorWidgetClass,menu,NULL);
    push = XtVaCreateManagedWidget("prevpage",xmPushButtonWidgetClass,menu,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,"PrevPage()");
    push = XtVaCreateManagedWidget("nextpage",xmPushButtonWidgetClass,menu,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,"NextPage()");
    XtVaCreateManagedWidget("sep",xmSeparatorWidgetClass,menu,NULL);
    push = XtVaCreateManagedWidget("zoomin",xmPushButtonWidgetClass,menu,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,"Zoom(inc)");
    push = XtVaCreateManagedWidget("zoomout",xmPushButtonWidgetClass,menu,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,"Zoom(dec)");

    /* menu - options */
    menu = XmCreatePulldownMenu(menubar,"optM",NULL,0);
    push = XtVaCreateManagedWidget("opt",xmCascadeButtonWidgetClass,menubar,
				   XmNsubMenuId,menu,NULL);
    smenu = XmCreatePulldownMenu(menu,"pcdM",NULL,0);
    XtVaCreateManagedWidget("pcd",xmCascadeButtonWidgetClass,menu,
			    XmNsubMenuId,smenu,NULL);
    push = XtVaCreateManagedWidget("1",xmToggleButtonWidgetClass,smenu,NULL);
    XtAddCallback(push,XmNvalueChangedCallback,pcd_cb,(XtPointer)1);
    push = XtVaCreateManagedWidget("2",xmToggleButtonWidgetClass,smenu,NULL);
    XtAddCallback(push,XmNvalueChangedCallback,pcd_cb,(XtPointer)2);
    push = XtVaCreateManagedWidget("3",xmToggleButtonWidgetClass,smenu,NULL);
    XtAddCallback(push,XmNvalueChangedCallback,pcd_cb,(XtPointer)3);
    push = XtVaCreateManagedWidget("4",xmToggleButtonWidgetClass,smenu,NULL);
    XtAddCallback(push,XmNvalueChangedCallback,pcd_cb,(XtPointer)4);
    push = XtVaCreateManagedWidget("5",xmToggleButtonWidgetClass,smenu,NULL);
    XtAddCallback(push,XmNvalueChangedCallback,pcd_cb,(XtPointer)5);
    pcd_set(smenu);

    push = XtVaCreateManagedWidget("autozoom",xmToggleButtonWidgetClass,menu,NULL);
    XtAddCallback(push,XmNvalueChangedCallback,cfg_bool_cb,NULL);
    XmToggleButtonSetState(push,GET_AUTOZOOM(),False);

    XtVaCreateManagedWidget("sep",xmSeparatorWidgetClass,menu,NULL);
    push = XtVaCreateManagedWidget("cfgsave",xmPushButtonWidgetClass,menu,NULL);
    XtAddCallback(push,XmNactivateCallback,cfg_save_cb,NULL);
    
    /* menu - help */
    menu = XmCreatePulldownMenu(menubar,"helpM",NULL,0);
    push = XtVaCreateManagedWidget("help",xmCascadeButtonWidgetClass,menubar,
				   XmNsubMenuId,menu,NULL);
    XtVaSetValues(menubar,XmNmenuHelpWidget,push,NULL);
    push = XtVaCreateManagedWidget("man",xmPushButtonWidgetClass,menu,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,"Man(ida)");
    XtVaCreateManagedWidget("sep",xmSeparatorWidgetClass,menu,NULL);
    push = XtVaCreateManagedWidget("about",xmPushButtonWidgetClass,menu,NULL);
    XtAddCallback(push,XmNactivateCallback,about_cb,NULL);

    /* toolbar */
    push = XtVaCreateManagedWidget("prev",xmPushButtonWidgetClass,tool,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,"Prev()");
    push = XtVaCreateManagedWidget("next",xmPushButtonWidgetClass,tool,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,"Next()");
    push = XtVaCreateManagedWidget("zoomin",xmPushButtonWidgetClass,tool,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,"Zoom(inc)");
    push = XtVaCreateManagedWidget("zoomout",xmPushButtonWidgetClass,tool,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,"Zoom(dec)");

    XtVaCreateManagedWidget("sep",xmSeparatorWidgetClass,tool,NULL);
    push = XtVaCreateManagedWidget("flipv",xmPushButtonWidgetClass,tool,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,"Filter(flip-vert)");
    push = XtVaCreateManagedWidget("fliph",xmPushButtonWidgetClass,tool,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,"Filter(flip-horz)");
    push = XtVaCreateManagedWidget("rotccw",xmPushButtonWidgetClass,tool,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,"Filter(rotate-ccw)");
    push = XtVaCreateManagedWidget("rotcw",xmPushButtonWidgetClass,tool,NULL);
    XtAddCallback(push,XmNactivateCallback,action_cb,"Filter(rotate-cw)");

    XtVaCreateManagedWidget("sep",xmSeparatorWidgetClass,tool,NULL);
    push = XtVaCreateManagedWidget("exit",xmPushButtonWidgetClass,tool,NULL);
    XtAddCallback(push,XmNactivateCallback,exit_cb,NULL);
}

/* ---------------------------------------------------------------------- */

void
resize_shell(void)
{
    char *title,*base;
    Dimension x,y,w,h,sw,sh;
    XmString str;
    int len;
    
    XtVaGetValues(app_shell, XtNx,&x, XtNy,&y, NULL);

    /* resize shell + move shell
       size: image size + 2*shadowThickness */
    w = ida->scrwidth+2;
    h = ida->scrheight+2;
    sw = XtScreen(ida->widget)->width;
    sh = XtScreen(ida->widget)->height;
    if (w > sw)
	w = sw;
    if (h > sh)
	h = sh;
    if (x+w > sw)
	x = sw-w;
    if (y+h > sh)
	y = sh-h;

    base = strrchr(ida->file,'/');
    if (base)
	base++;
    else
	base = ida->file;
    title = malloc(strlen(base)+128);
    len = sprintf(title,"%s (%dx%d", base,
		  ida->img.i.width, ida->img.i.height);
    if (ida->img.i.dpi)
	len += sprintf(title+len," | %d dpi",
		       ida->img.i.dpi);
    if (ida->img.i.npages > 1)
        len += sprintf(title+len," | page %d/%d",
		       cpage+1, ida->img.i.npages);
    len += sprintf(title+len," | %d%%)", viewer_i2s(ida->zoom,100));
    XtVaSetValues(app_shell, XtNtitle,title,
		  /* XtNx,x, XtNy,y, */ XtNwidth,w, XtNheight,h,
		  NULL);
    str = XmStringGenerate(title,NULL,XmMULTIBYTE_TEXT,NULL);
    XtVaSetValues(status,XmNlabelString,str,NULL);
    XmStringFree(str);
    free(title);
}

static int
load_file(int nr, int np)
{
    if(nr < 0 || nr >= nfiles)
        return -1;
    npages = viewer_loadimage(ida,files[nr],np);
    if (-1 == npages)
	return -1;
    resize_shell();
#if 0
    XmListSelectPos(wlist,nr+1,False);
    cfile = nr;
#endif
    return npages;
}

char*
load_tmpfile(char *base)
{
    char *tmpdir;
    char *filename;

    tmpdir = getenv("TMPDIR");
    if (NULL == tmpdir)
	tmpdir="/tmp";
    filename = malloc(strlen(tmpdir)+strlen(base)+16);
    sprintf(filename,"%s/%s-XXXXXX",tmpdir,base);
    return filename;
}

static void
load_logo(void)
{
    static unsigned char logo[] = {
#include "logo.h"
    };
    char *filename = load_tmpfile("ida-logo");
    int fd;
    fd = mkstemp(filename);
    write(fd,logo,sizeof(logo));
    close(fd);
    cpage = 0;
    npages = 1;
    if (0 < viewer_loadimage(ida,filename,cpage)) {
	ida->file = "ida " VERSION;
	resize_shell();
    }
    unlink(filename);
    free(filename);
}

static void
load_stdin(void)
{
    char *filename = load_tmpfile("ida-stdin");
    char buf[4096];
    int rc,fd;
    fd = mkstemp(filename);
    for (;;) {
	rc = read(0,buf,sizeof(buf));
	if (rc <= 0)
	    break;
	write(fd,buf,rc);
    }
    close(fd);
    cpage = 0;
    npages = 1;
    if (0 < viewer_loadimage(ida,filename,cpage)) {
	ida->file = "stdin";
	resize_shell();
    }
    unlink(filename);
    free(filename);
}

void
next_ac(Widget widget, XEvent *event, String *params, Cardinal *num)
{
    for (;;) {
	if (cfile >= nfiles-1)
	    return;
	cfile++;
        cpage = 0;
	if (0 <= load_file(cfile,cpage))
	    break;
    }
}

void
prev_ac(Widget widget, XEvent *event, String *params, Cardinal *num)
{
    for (;;) {
	if (cfile < 1)
	    return;
	cfile--;
        cpage = 0;
	if (0 <= load_file(cfile,cpage))
	    break;
    }
}

void
next_page_ac(Widget widget, XEvent *event, String *params, Cardinal *num)
{
    for (;;) {
	if (cpage >= npages-1)
	    return;
	cpage++;
	if (0 <= load_file(cfile,cpage))
	    break;
    }
}

void
prev_page_ac(Widget widget, XEvent *event, String *params, Cardinal *num)
{
    for (;;) {
	if (cpage <= 0)
	    return;
	cpage--;
	if (0 <= load_file(cfile,cpage))
	    break;
    }
}

void
zoom_ac(Widget widget, XEvent *event, String *params, Cardinal *num)
{
    int zoom;
    
    if (0 == *num)
	return;

    if (0 == strcasecmp(params[0],"auto")) {
	viewer_autozoom(ida);
	return;
    }

    if (0 == strcasecmp(params[0],"inc")) {
	zoom = ida->zoom+1;
    } else if (0 == strcasecmp(params[0],"dec")) {
	zoom = ida->zoom-1;
    } else {
	zoom = atoi(params[0]);
    }
    viewer_setzoom(ida,zoom);
    resize_shell();
}

void
scroll_ac(Widget widget, XEvent *event, String *params, Cardinal *num)
{
    fprintf(stderr,"Scroll(): %s\n",XtName(widget));
}

/* ---------------------------------------------------------------------- */

void new_file(char *name, int complain)
{
    struct stat st;
    int n;

    if (curl_is_url(name))
	goto load;

    if (0 == strncasecmp(name,"file:",5))
	name += 5;
    if (-1 == stat(name,&st)) {
	if (complain)
	    fprintf(stderr,"stat %s: %s\n",name,strerror(errno));
	return;
    }
    switch (st.st_mode & S_IFMT) {
    case S_IFDIR:
	browser_window(name);
	break;
    case S_IFREG:
	goto load;
	break;
    }
    return;
    
 load:
    n = list_append(name);
    list_update();
    cpage = 0;
    load_file(n,cpage);
}

static void
load_done_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
{
    XmFileSelectionBoxCallbackStruct *cb = call_data;
    char *line;

    if (cb->reason == XmCR_OK) {
        line = XmStringUnparse(cb->value,NULL,
                               XmMULTIBYTE_TEXT,XmMULTIBYTE_TEXT,
                               NULL,0,0);
	new_file(line,1);
    }
    XtUnmanageChild(widget);
}

void
load_ac(Widget widget, XEvent *event, String *params, Cardinal *num)
{
    Widget help;

    if (NULL == loadbox) {
	loadbox = XmCreateFileSelectionDialog(app_shell,"load",NULL,0);
	help = XmFileSelectionBoxGetChild(loadbox,XmDIALOG_HELP_BUTTON);
	XtUnmanageChild(help);
	XtAddCallback(loadbox,XmNokCallback,load_done_cb,NULL);
	XtAddCallback(loadbox,XmNcancelCallback,load_done_cb,NULL);
    } else {
	XmFileSelectionDoSearch(loadbox,NULL);
    }
    XtManageChild(loadbox);
}

void
scan_ac(Widget widget, XEvent *event, String *params, Cardinal *num)
{
#ifdef HAVE_LIBSANE
    cpage = 0;
    if (*num)
	npages = viewer_loader_start(ida, &sane_loader, NULL, params[0], 0);
    else
	npages = viewer_loader_start(ida, &sane_loader, NULL, "", 0);
    if (-1 == npages)
	return;
    ida->file = "scanned image";
    resize_shell();
#endif
}

/* ---------------------------------------------------------------------- */

void
do_save_print(void)
{
    FILE *fp;
    
    if (save_filename) {
	XtUnmanageChild(savebox);
	ptr_busy();
	if (NULL == (fp = fopen(save_filename,"wb"))) {
	    fprintf(stderr,"save: can't open %s: %s\n",
		    save_filename,strerror(errno));
	} else if (-1 == cwriter->write(fp,&ida->img)) {
	    fclose(fp);
	    fprintf(stderr,"saving %s FAILED",save_filename);
	} else {
	    fclose(fp);
	    list_append(save_filename);
	    list_update();
	}
	ptr_idle();
    }
    if (print_command) {
	XtUnmanageChild(printbox);
	ptr_busy();
	if (NULL == (fp = popen(print_command,"w"))) {
	    fprintf(stderr,"print: can't exec %s: %s\n",
		    print_command,strerror(errno));
	} else {
	    if (-1 == cwriter->write(fp,&ida->img))
		fprintf(stderr,"printing FAILED");
	    fclose(fp);
	}
	ptr_idle();
    }
}

static void
save_done_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
{
    XmFileSelectionBoxCallbackStruct *cb = call_data;

    if (cb->reason == XmCR_OK) {
	print_command = NULL;
	save_filename = XmStringUnparse(cb->value,NULL,
					XmMULTIBYTE_TEXT,XmMULTIBYTE_TEXT,
					NULL,0,0);
	if (cwriter->conf) {
	    cwriter->conf(widget,&ida->img);
	} else {
	    do_save_print();
	}
    } else {
	XtUnmanageChild(widget);
    }
}

static void
save_fmt_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
{
    cwriter = clientdata;
}

static void
save_ext_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
{
    Widget option = clientdata;
    Widget menu;
    WidgetList children;
    Cardinal nchildren;
    struct ida_writer *wr = NULL;
    struct list_head *item;
    char *name,*ext;
    int i,j,pick;

    name = XmTextGetString(widget);
    ext = strrchr(name,'.');
    if (NULL == ext)
	return;
    if (strchr(ext,'/'))
	return;
    ext++;

    i = 0; pick = -1;
    list_for_each(item,&writers) {
	wr = list_entry(item, struct ida_writer, list);
	for (j = 0; NULL != wr->ext[j]; j++)
	    if (0 == strcasecmp(ext,wr->ext[j]))
		pick = i;
	if (-1 != pick)
	    break;
	i++;
    }
    if (-1 == pick)
	return;

    XtVaGetValues(option,XmNsubMenuId,&menu,NULL);
    XtVaGetValues(menu,XtNchildren,&children,
		  XtNnumChildren,&nchildren,NULL);
    XtVaSetValues(option,XmNmenuHistory,children[pick],NULL);
    cwriter = wr;
}

static void
save_ac(Widget widget, XEvent *event, String *params, Cardinal *num)
{
    Widget help,menu,option,push,text;
    Arg args[2];
    struct ida_writer *wr = NULL;
    struct list_head *item;

    if (NULL == savebox) {
	savebox = XmCreateFileSelectionDialog(app_shell,"save",NULL,0);
	help = XmFileSelectionBoxGetChild(savebox,XmDIALOG_HELP_BUTTON);
	text = XmFileSelectionBoxGetChild(savebox,XmDIALOG_TEXT);
	XtUnmanageChild(help);

	menu = XmCreatePulldownMenu(savebox,"formatM",NULL,0);
	XtSetArg(args[0],XmNsubMenuId,menu);
	option = XmCreateOptionMenu(savebox,"format",args,1);
	XtManageChild(option);
	list_for_each(item,&writers) {
	    wr = list_entry(item, struct ida_writer, list);
	    push = XtVaCreateManagedWidget(wr->label,
					   xmPushButtonWidgetClass,menu,
					   NULL);
	    XtAddCallback(push,XmNactivateCallback,save_fmt_cb,wr);
	}
	cwriter = list_entry(writers.next, struct ida_writer, list);

	XtAddCallback(text,XmNvalueChangedCallback,save_ext_cb,option);
	XtAddCallback(savebox,XmNokCallback,save_done_cb,NULL);
	XtAddCallback(savebox,XmNcancelCallback,save_done_cb,NULL);
    } else {
	XmFileSelectionDoSearch(savebox,NULL);
    }
    XtManageChild(savebox);
}

/* ---------------------------------------------------------------------- */

static void
print_done_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
{
    XmSelectionBoxCallbackStruct *cb = call_data;

    if (cb->reason == XmCR_OK) {
	save_filename = NULL;
	print_command = XmStringUnparse(cb->value,NULL,
					XmMULTIBYTE_TEXT,XmMULTIBYTE_TEXT,
					NULL,0,0);
	cwriter = &ps_writer;
	cwriter->conf(widget,&ida->img);
    } else {
	XtUnmanageChild(widget);
    }
}

static void
print_ac(Widget widget, XEvent *event, String *params, Cardinal *num)
{
    if (NULL == printbox) {
	printbox = XmCreatePromptDialog(app_shell,"print",NULL,0);
	XtUnmanageChild(XmSelectionBoxGetChild(printbox,XmDIALOG_HELP_BUTTON));
	XtAddCallback(printbox,XmNokCallback,print_done_cb,NULL);
	XtAddCallback(printbox,XmNcancelCallback,print_done_cb,NULL);
    }
    XtManageChild(printbox);
}

/* ---------------------------------------------------------------------- */

static struct ida_op *ops[] = {
    &desc_flip_vert,
    &desc_flip_horz,
    &desc_rotate_cw,
    &desc_rotate_ccw,
    &desc_invert,
    &desc_crop,
    &desc_autocrop,
    &desc_grayscale,
    NULL
};

void
filter_ac(Widget widget, XEvent *event, String *params, Cardinal *num)
{
    struct ida_op *op = NULL;
    int i;

    if (*num < 1)
	return;
    for (i = 0; NULL != ops[i]; i++) {
	op = ops[i];
	if (0 == strcasecmp(op->name,params[0]))
	    break;
    }
    if (NULL == ops[i]) {
	fprintf(stderr,"Oops: unknown filter: %s\n",params[0]);
	return;
    }

    viewer_start_op(ida,op,NULL);
    if (ida->op_src.i.width  != ida->img.i.width ||
	ida->op_src.i.height != ida->img.i.height)
	resize_shell();
}

void
f3x3_ac(Widget widget, XEvent *event, String *params, Cardinal *num)
{
    struct op_3x3_parm p;

    if (*num < 9) {
	fprintf(stderr,"F3x3: wrong number of args (%d, need 9)\n",*num);
	return;
    }
    memset(&p,0,sizeof(p));
    p.f1[0] = atoi(params[0]);
    p.f1[1] = atoi(params[1]);
    p.f1[2] = atoi(params[2]);
    p.f2[0] = atoi(params[3]);
    p.f2[1] = atoi(params[4]);
    p.f2[2] = atoi(params[5]);
    p.f3[0] = atoi(params[6]);
    p.f3[1] = atoi(params[7]);
    p.f3[2] = atoi(params[8]);
    if (*num >  9) p.mul = atoi(params[ 9]);
    if (*num > 10) p.div = atoi(params[10]);
    if (*num > 11) p.add = atoi(params[11]);
    if (debug) {
	fprintf(stderr,"f3x3: -----------\n");
	fprintf(stderr,"f3x3: %3d %3d %3d\n",p.f1[0],p.f1[1],p.f1[2]);
	fprintf(stderr,"f3x3: %3d %3d %3d\n",p.f2[0],p.f2[1],p.f2[2]);
	fprintf(stderr,"f3x3: %3d %3d %3d\n",p.f3[0],p.f3[1],p.f3[2]);
	fprintf(stderr,"f3x3: *%d/%d+%d\n",p.mul,p.div,p.add);
    }
    viewer_start_op(ida,&desc_3x3,&p);
}

void
undo_ac(Widget widget, XEvent *event, String *params, Cardinal *num)
{
    Widget msgbox;
    int resize;

    resize = (ida->undo.i.width  != ida->img.i.width ||
	      ida->undo.i.height != ida->img.i.height);
    if (-1 == viewer_undo(ida)) {
	msgbox = XmCreateInformationDialog(app_shell,"noundobox",NULL,0);
	XtUnmanageChild(XmMessageBoxGetChild(msgbox,XmDIALOG_HELP_BUTTON));
	XtUnmanageChild(XmMessageBoxGetChild(msgbox,XmDIALOG_CANCEL_BUTTON));
	XtAddCallback(msgbox,XmNokCallback,destroy_cb,msgbox);
	XtManageChild(msgbox);
    } else {
	if (resize)
	    resize_shell();
    }
}

/* ---------------------------------------------------------------------- */

struct ida_prompt {
    Widget shell;
    Widget box;
    Widget scale;
    Widget text;
    int apply;
    int value;
    int decimal;
    int factor;    /* 10^decimal */
    void (*notify)(int value, int preview);
};

static void
prompt_setvalue(struct ida_prompt *me, int value, int scale, int text)
{
    char str[32];
    int min,max;
    
    if (me->value == value)
	return;
    XtVaGetValues(me->scale,XmNminimum,&min,XmNmaximum,&max,NULL);
    if (value < min || value > max)
	return;

    me->value = value;
    if (scale)
	XmScaleSetValue(me->scale,value);
    if (text) {
	if (me->decimal) {
	    sprintf(str,"%*.*f",me->decimal+2,me->decimal,
		    (float)value/me->factor);
	} else {
	    sprintf(str,"%d",value);
	}
	XmTextSetString(me->text,str);
    }
    if (me->notify)
	me->notify(value,1);
}

static void
prompt_scale_cb(Widget widget, XtPointer client_data, XtPointer calldata)
{
    struct ida_prompt *me = client_data;
    XmScaleCallbackStruct *cd = calldata;

    prompt_setvalue(me,cd->value,0,1);
}

static void
prompt_text_cb(Widget widget, XtPointer client_data, XtPointer calldata)
{
    struct ida_prompt *me = client_data;
    float fvalue;
    int value;

    if (me->decimal) {
	fvalue  = atof(XmTextGetString(me->text));
	fvalue += 0.5/me->factor;
	value = (int)(fvalue * me->factor);
    } else {
	value = atoi(XmTextGetString(me->text));
    }
    prompt_setvalue(me,value,1,0);
}

static void
prompt_box_cb(Widget widget, XtPointer client_data, XtPointer calldata)
{
    struct ida_prompt *me = client_data;
    XmSelectionBoxCallbackStruct *cd = calldata;

    if (XmCR_OK == cd->reason)
	me->apply = 1;
    XtDestroyWidget(me->shell);
}

static void
prompt_shell_cb(Widget widget, XtPointer client_data, XtPointer calldata)
{
    struct ida_prompt *me = client_data;

    if (me->apply)
	me->notify(me->value,0);
    else
	viewer_cancel_preview(ida);
    free(me);
}

static void
prompt_init(char *name, int decimal, int value,
	    void (*notify)(int value, int preview))
{
    struct ida_prompt *me;

    me = malloc(sizeof(*me));
    memset(me,0,sizeof(*me));
    if (decimal) {
	int i;
	me->decimal = decimal;
	me->factor = 1;
	for (i = 0; i < decimal; i++)
	    me->factor *= 10;
    }
    me->notify = notify;
    
    me->box = XmCreatePromptDialog(app_shell,name,NULL,0);
    me->shell = XtParent(me->box);
    me->text = XmSelectionBoxGetChild(me->box,XmDIALOG_TEXT);
    XmdRegisterEditres(XtParent(me->box));
    XtUnmanageChild(XmSelectionBoxGetChild(me->box,XmDIALOG_HELP_BUTTON));
    me->scale = XtVaCreateManagedWidget("scale",xmScaleWidgetClass,
					me->box,NULL);

    XtAddCallback(me->scale,XmNdragCallback,prompt_scale_cb,me);
    XtAddCallback(me->scale,XmNvalueChangedCallback,prompt_scale_cb,me);
    XtAddCallback(me->text,XmNvalueChangedCallback,prompt_text_cb,me);
    XtAddCallback(me->box,XmNokCallback,prompt_box_cb,me);
    XtAddCallback(me->box,XmNcancelCallback,prompt_box_cb,me);
    XtAddCallback(me->shell,XmNdestroyCallback,prompt_shell_cb,me);
    
    XtManageChild(me->box);
    prompt_setvalue(me,value,1,1);
}

/* ---------------------------------------------------------------------- */

static void
gamma_notify(int value, int preview)
{
    struct op_map_parm param;
    float gamma = (float)value/100;

    param.red = op_map_nothing;
    param.red.gamma   = gamma;
    param.green = param.red;
    param.blue  = param.red;
    if (preview) {
	viewer_start_preview(ida,&desc_map,&param);
    } else {
	gamma_val = value;
	viewer_start_op(ida,&desc_map,&param);
    }
}

void
gamma_ac(Widget widget, XEvent *event, String *params, Cardinal *num)
{
    prompt_init("gamma",2,gamma_val,gamma_notify);
}

static void
bright_notify(int value, int preview)
{
    struct op_map_parm param;

    param.red = op_map_nothing;
    param.red.bottom += value;
    param.red.top    += value;
    param.green = param.red;
    param.blue  = param.red;
    if (preview) {
	viewer_start_preview(ida,&desc_map,&param);
    } else {
	bright_val = value;
	viewer_start_op(ida,&desc_map,&param);
    }
}

void
bright_ac(Widget widget, XEvent *event, String *params, Cardinal *num)
{
    prompt_init("bright",0,bright_val,bright_notify);
}

static void
contrast_notify(int value, int preview)
{
    struct op_map_parm param;

    param.red = op_map_nothing;
    param.red.bottom -= value;
    param.red.top    += value;
    param.green = param.red;
    param.blue  = param.red;
    if (preview) {
	viewer_start_preview(ida,&desc_map,&param);
    } else {
	contrast_val = value;
	viewer_start_op(ida,&desc_map,&param);
    }
}

void
contrast_ac(Widget widget, XEvent *event, String *params, Cardinal *num)
{
    prompt_init("contrast",0,contrast_val,contrast_notify);
}

static void
rotate_notify(int value, int preview)
{
    struct op_rotate_parm parm;

    parm.angle = value;
    if (preview) {
	viewer_start_preview(ida,&desc_rotate,&parm);
    } else {
	rotate_val = value;
	viewer_start_op(ida,&desc_rotate,&parm);
    }
}

void
rotate_ac(Widget widget, XEvent *event, String *params, Cardinal *num)
{
    prompt_init("rotate",0,rotate_val,rotate_notify);
}

static void
sharpe_notify(int value, int preview)
{
    struct op_sharpe_parm parm;

    parm.factor = value;
    if (preview) {
	viewer_start_preview(ida,&desc_sharpe,&parm);
    } else {
	sharpe_val = value;
	viewer_start_op(ida,&desc_sharpe,&parm);
    }
}

void
sharpe_ac(Widget widget, XEvent *event, String *params, Cardinal *num)
{
    prompt_init("sharpe",0,sharpe_val,sharpe_notify);
}

void
color_ac(Widget widget, XEvent *event, String *params, Cardinal *num)
{
    color_init(&ida->img);
}

/* ---------------------------------------------------------------------- */

struct ida_resize {
    Widget dlg,tx,ty,tr,lock,size,res,label;
    int yupdate,xupdate,rupdate;
    int apply;
};

static void
resize_phys_size(struct ida_resize *h)
{
    char buf[128];
    XmString str;
    int dpi;
    float x,y;

    dpi = atoi(XmTextGetString(h->tr));
    if (dpi) {
	x = (float)atoi(XmTextGetString(h->tx)) / dpi;
	y = (float)atoi(XmTextGetString(h->ty)) / dpi;
	sprintf(buf,"%.2f x %.2f inch\n%.2f x %.2f cm",
		x,y, x*2.54, y*2.54);
    } else {
	strcpy(buf,"unknown");
    }
    str = XmStringGenerate(buf, NULL, XmMULTIBYTE_TEXT,NULL);
    XtVaSetValues(h->label,XmNlabelString,str,NULL);
}

static void
resize_sync_cb(Widget widget, XtPointer client_data, XtPointer calldata)
{
    struct ida_resize *h = client_data;
    char buf[32];
    int i,lock,res;

    lock = XmToggleButtonGetState(h->lock);
    res  = XmToggleButtonGetState(h->res);

    /* update text fields */
    if (h->tx == widget) {
	if (h->xupdate) {
	    h->xupdate--;
	    return;
	}
	i = atoi(XmTextGetString(h->tx));
	if (lock) {
	    sprintf(buf,"%d",i * ida->img.i.height / ida->img.i.width);
	    h->yupdate++;
	    XmTextSetString(h->ty,buf);
	    if (res) {
		sprintf(buf,"%d", ida->img.i.dpi * i / ida->img.i.width);
		h->rupdate++;
		XmTextSetString(h->tr,buf);
	    }
	} else {
	    if (res) {
		h->rupdate++;
		XmTextSetString(h->tr,"0");
	    }
	}
	resize_phys_size(h);
    }
    if (h->ty == widget) {
	if (h->yupdate) {
	    h->yupdate--;
	    return;
	}
	i = atoi(XmTextGetString(h->ty));
	if (lock) {
	    sprintf(buf,"%d",i * ida->img.i.width / ida->img.i.height);
	    h->xupdate++;
	    XmTextSetString(h->tx,buf);
	    if (res) {
		sprintf(buf,"%d", ida->img.i.dpi * i / ida->img.i.height);
		h->rupdate++;
		XmTextSetString(h->tr,buf);
	    }
	} else {
	    if (res) {
		h->rupdate++;
		XmTextSetString(h->tr,"0");
	    }
	}
	resize_phys_size(h);
    }
    if (h->tr == widget) {
	if (h->rupdate) {
	    h->rupdate--;
	    return;
	}
	i = atoi(XmTextGetString(h->tr));
	sprintf(buf,"%d", ida->img.i.width * i / ida->img.i.dpi);
	h->xupdate++;
	XmTextSetString(h->tx,buf);
	sprintf(buf,"%d", ida->img.i.height * i / ida->img.i.dpi);
	h->yupdate++;
	XmTextSetString(h->ty,buf);
	resize_phys_size(h);
    }

    /* radio buttons pressed */
    if (h->size == widget && XmToggleButtonGetState(h->size)) {
	XmToggleButtonSetState(h->res,0,False);
	sprintf(buf,"%d", ida->img.i.dpi);
	h->rupdate++;
	XmTextSetString(h->tr,buf);
	XtVaSetValues(h->tr,XmNsensitive,False,NULL);
	resize_phys_size(h);
    }
    if (h->res == widget && XmToggleButtonGetState(h->res)) {
	XmToggleButtonSetState(h->size,0,False);
	XtVaSetValues(h->tr,XmNsensitive,True,NULL);
    }
}

static void
resize_button_cb(Widget widget, XtPointer client_data, XtPointer calldata)
{
    struct ida_resize *h = client_data;
    XmSelectionBoxCallbackStruct *cb = calldata;

    if (cb->reason == XmCR_OK)
	h->apply = 1;
    XtDestroyWidget(XtParent(h->dlg));
}

static void
resize_destroy(Widget widget, XtPointer client_data, XtPointer calldata)
{
    struct ida_resize *h = client_data;
    struct op_resize_parm param;

    if (!h->apply)
	return;
    param.width  = atoi(XmTextGetString(h->tx));
    param.height = atoi(XmTextGetString(h->ty));
    param.dpi    = atoi(XmTextGetString(h->tr));
    if (0 == param.width  ||
	0 == param.height) {
	fprintf(stderr,"resize: invalid argument\n");
	return;
    }
	
    viewer_start_op(ida,&desc_resize,&param);
    resize_shell();
    free(h);
}

static void
resize_ac(Widget widget, XEvent *event, String *params, Cardinal *num)
{
    Widget rc,rc2;
    char buf[32];
    struct ida_resize *h;

    h = malloc(sizeof(*h));
    memset(h,0,sizeof(*h));
    
    h->dlg = XmCreatePromptDialog(app_shell,"resize",NULL,0);
    XmdRegisterEditres(XtParent(h->dlg));
    XtUnmanageChild(XmSelectionBoxGetChild(h->dlg,XmDIALOG_SELECTION_LABEL));
    XtUnmanageChild(XmSelectionBoxGetChild(h->dlg,XmDIALOG_HELP_BUTTON));
    XtUnmanageChild(XmSelectionBoxGetChild(h->dlg,XmDIALOG_TEXT));
    rc = XtVaCreateManagedWidget("rc", xmRowColumnWidgetClass,h->dlg, NULL);
    XtVaCreateManagedWidget("lx", xmLabelWidgetClass,rc, NULL);
    h->tx = XtVaCreateManagedWidget("tx", xmTextWidgetClass,rc, NULL);
    XtVaCreateManagedWidget("ly", xmLabelWidgetClass,rc, NULL);
    h->ty = XtVaCreateManagedWidget("ty", xmTextWidgetClass,rc, NULL);
    XtVaCreateManagedWidget("lr", xmLabelWidgetClass,rc, NULL);
    h->tr = XtVaCreateManagedWidget("tr", xmTextWidgetClass,rc, NULL);
    h->lock = XtVaCreateManagedWidget("lock", xmToggleButtonWidgetClass,
				      rc, NULL);
    rc2 = XtVaCreateManagedWidget("rc", xmRowColumnWidgetClass,rc, NULL);
    h->size = XtVaCreateManagedWidget("size", xmToggleButtonWidgetClass,
				      rc2, NULL);
    h->res  = XtVaCreateManagedWidget("res", xmToggleButtonWidgetClass,
				      rc2, NULL);
    XtVaCreateManagedWidget("phys", xmLabelWidgetClass,rc,NULL);
    h->label = XtVaCreateManagedWidget("label", xmLabelWidgetClass,
				       rc, NULL);

    sprintf(buf,"%d",ida->img.i.width);
    XmTextSetString(h->tx,buf);
    sprintf(buf,"%d",ida->img.i.height);
    XmTextSetString(h->ty,buf);
    sprintf(buf,"%d",ida->img.i.dpi);
    XmTextSetString(h->tr,buf);
    XtVaSetValues(h->tr,XmNsensitive,False,NULL);
    XmToggleButtonSetState(h->lock,1,False);
    XmToggleButtonSetState(h->size,1,False);
    XmToggleButtonSetState(h->res,0,False);
    if (!ida->img.i.dpi) {
    	XtVaSetValues(h->size,XmNsensitive,False,NULL);
	XtVaSetValues(h->res, XmNsensitive,False,NULL);
    }
    resize_phys_size(h);
    
    XtAddCallback(XtParent(h->dlg),XmNdestroyCallback,resize_destroy,h);
    XtAddCallback(h->dlg, XmNokCallback,           resize_button_cb, h);
    XtAddCallback(h->dlg, XmNcancelCallback,       resize_button_cb, h);
    XtAddCallback(h->tx,  XmNvalueChangedCallback, resize_sync_cb,   h);
    XtAddCallback(h->ty,  XmNvalueChangedCallback, resize_sync_cb,   h);
    XtAddCallback(h->tr,  XmNvalueChangedCallback, resize_sync_cb,   h);
    XtAddCallback(h->size,XmNvalueChangedCallback, resize_sync_cb,   h);
    XtAddCallback(h->res, XmNvalueChangedCallback, resize_sync_cb,   h);
    XtManageChild(h->dlg);
}

/* ---------------------------------------------------------------------- */

struct stderr_handler {
    Widget box;
    XmString str;
    int pipe,err;
    XtInputId id;
};

static void
stderr_input(XtPointer clientdata, int *src, XtInputId *id)
{
    struct stderr_handler *h = clientdata;
    XmString item;
    Widget label;
    char buf[1024];
    int rc;

    rc = read(h->pipe,buf,sizeof(buf)-1);
    if (rc <= 0) {
	/* Oops */
	XtRemoveInput(h->id);
	close(h->pipe);
	XtDestroyWidget(h->box);
	free(h);
    }
    buf[rc] = 0;
    write(h->err,buf,rc);
    item = XmStringGenerate(buf, NULL, XmMULTIBYTE_TEXT,NULL);
    h->str = XmStringConcatAndFree(h->str,item);
    label = XmMessageBoxGetChild(h->box,XmDIALOG_MESSAGE_LABEL);
    XtVaSetValues(label,XmNlabelString,h->str,NULL);
    XtManageChild(h->box);
}

static void
stderr_ok_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
{
    struct stderr_handler *h = clientdata;

    XmStringFree(h->str);
    h->str = XmStringGenerate("", NULL, XmMULTIBYTE_TEXT,NULL);
    XtUnmanageChild(h->box);
}

static void
stderr_close_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
{
    struct stderr_handler *h = clientdata;

    XmStringFree(h->str);
    h->str = XmStringGenerate("", NULL, XmMULTIBYTE_TEXT,NULL);
}

static void
stderr_init(void)
{
    struct stderr_handler *h;
    int p[2];

    h = malloc(sizeof(*h));
    memset(h,0,sizeof(*h));
    h->str = XmStringGenerate("", NULL, XmMULTIBYTE_TEXT,NULL);
    h->box = XmCreateErrorDialog(app_shell,"errbox",NULL,0);
    XtUnmanageChild(XmMessageBoxGetChild(h->box,XmDIALOG_HELP_BUTTON));
    XtUnmanageChild(XmMessageBoxGetChild(h->box,XmDIALOG_CANCEL_BUTTON));
    XtAddCallback(h->box,XmNokCallback,stderr_ok_cb,h);
    XtAddCallback(XtParent(h->box),XmNpopdownCallback,stderr_close_cb,h);
    XSync(XtDisplay(app_shell),False);
    if (!debug) {
	pipe(p);
	h->err = dup(2);
	dup2(p[1],2);
	close(p[1]);
	h->pipe = p[0];
	h->id = XtAppAddInput(app_context,h->pipe,(XtPointer)XtInputReadMask,
			      stderr_input,h);
    }
}

/* ---------------------------------------------------------------------- */

static void
create_mainwindow(void)
{
    Widget img;

    XmdRegisterEditres(app_shell);
    view = XmCreateScrolledWindow(app_shell,"view",NULL,0);
    XtManageChild(view);
    img = XtVaCreateManagedWidget("image", xmDrawingAreaWidgetClass,view,NULL);
    XtAddCallback(img,XmNdestinationCallback,selection_dest,NULL);
    XtAddCallback(img,XmNconvertCallback,selection_convert,NULL);
    dnd_add(img);
    ida = viewer_init(img);
    XtInstallAllAccelerators(img,app_shell);
}

static void
usage(void)
{
    fprintf(stderr,
	    "ida " VERSION " - image viewer & editor\n"
	    "usage: ida [ options ] [ files ]\n"
	    "options:\n"
	    "   -h, -help    this text\n"
	    "       -pcd n   pick PhotoCD size (n = 1 .. 5, default 3)\n"
	    "   -d, -debug   enable debug messages\n");
    exit(0);
}

int
main(int argc, char *argv[])
{
    int i, files, zero = 0;
    struct stat st;
    Pixel background;

    setlocale(LC_ALL,"");
    if (0 == strcasecmp("utf-8", nl_langinfo(CODESET))) {
	/* ### FIXME ###
	 * for not-yet known reasons ida crashes somewhere deep in
	 * the Motif libraries when running in utf-8 locale ... */
	setenv("LC_ALL", "POSIX", 1);
	setlocale(LC_ALL,"");
    }
    
    binary = argv[0];
    ida_init_config();
    ida_read_config();

    XtSetLanguageProc(NULL,NULL,NULL);
    app_shell = XtAppInitialize(&app_context, "Ida",
				opt_desc, opt_count,
				&argc, argv,
				fallback_ressources,
				NULL, 0);
    dpy = XtDisplay(app_shell);
    XtGetApplicationResources(app_shell,&args,
			      args_desc,args_count,
			      NULL,0);
    pcd_res  = GET_PHOTOCD_RES();
    sane_res = GET_SANE_RES();
    if (args.help)
	usage();
    if (args.debug) {
	debug=1;
	xdnd_debug = 1;
	XSynchronize(dpy,1);
    }

    XtAppAddActions(app_context, actionTable,
		    sizeof(actionTable) / sizeof(XtActionsRec));
    if (0) {
	XtAddCallback(XmGetXmDisplay(dpy),XmNnoFontCallback,
		      display_cb,NULL);
	XtAddCallback(XmGetXmDisplay(dpy),XmNnoRenditionCallback,
		      display_cb,NULL);
    }
    XtVaGetValues(app_shell, XtNbackground,&background, NULL);
    x11_color_init(app_shell,&gray);
    x11_icons_init(dpy, background /* x11_gray */);
    stderr_init();
    ipc_init();

    wm_delete_window = XInternAtom(dpy,"WM_DELETE_WINDOW",False);
    create_mainwindow();
    create_control();
    XtRealizeWidget(app_shell);
    ptr_register(ida->widget);
    ptr_register(control_shell);

    /* handle cmd line args */
    if (2 == argc && 0 == strcmp(argv[1],"-")) {
	load_stdin();
    } else if (argc > 1) {
	for (files = 0, i = 1; i < argc; i++) {
 	    if (curl_is_url(argv[i])) {
		list_append(argv[i]);
		files++;
		continue;
	    }
	    if (-1 == stat(argv[i],&st)) {
		if (debug)
		    fprintf(stderr,"stat %s: %s\n",argv[i],strerror(errno));
		continue;
	    }
	    switch (st.st_mode & S_IFMT) {
	    case S_IFDIR:
		browser_window(argv[i]);
		break;
	    case S_IFREG:
		list_append(argv[i]);
		files++;
		break;
	    }
	}
	if (files) {
	    list_update();
	    next_ac(ida->widget,NULL,NULL,&zero);
	}
    }

    if (NULL == ida->file)
	load_logo();

    XtAppMainLoop(app_context);
    return 0; /* keep compiler happy */
}
