/*****************************************************************************
   Major portions of this software are copyrighted by the Medical College
   of Wisconsin, 1994-2000, and are released under the Gnu General Public
   License, Version 2.  See the file README.Copyright for details.
******************************************************************************/

#include "vecmat.h"

#include "afni.h"

#ifndef ALLOW_PLUGINS
#  error "Plugins not properly set up -- see machdep.h"
#endif

/***********************************************************************
  Plugin to nudge a dataset around.
  Makes a custom interface.
  -- RWCox -- April 2000
************************************************************************/

/*----------------- prototypes for internal routines -----------------*/

static PLUGIN_interface * plint = NULL ;

char * NUD_main( PLUGIN_interface * ) ;  /* the entry point */

static void NUD_make_widgets(void) ;

static void NUD_nudge_CB ( Widget , XtPointer , XtPointer ) ;
static void NUD_clear_CB ( Widget , XtPointer , XtPointer ) ;
static void NUD_undo_CB  ( Widget , XtPointer , XtPointer ) ;
static void NUD_redo_CB  ( Widget , XtPointer , XtPointer ) ;
static void NUD_help_CB  ( Widget , XtPointer , XtPointer ) ;
static void NUD_quit_CB  ( Widget , XtPointer , XtPointer ) ;
static void NUD_doall_CB ( Widget , XtPointer , XtPointer ) ;
static void NUD_choose_CB( Widget , XtPointer , XtPointer ) ;
static void NUD_print_CB ( Widget , XtPointer , XtPointer ) ;

static void NUD_finalize_dset_CB( Widget , XtPointer , MCW_choose_cbs * ) ;

static void NUD_brick_av_CB( MCW_arrowval * , XtPointer ) ; /* Sub-brick menu */

static void NUD_undopush(void) ;
static void NUD_setcumlab(void) ;

static void NUD_rotate( MRI_IMAGE * im ) ;
static void NUD_update_base( Widget ) ;

/***********************************************************************
   Set up the interface to the user
************************************************************************/


DEFINE_PLUGIN_PROTOTYPE

PLUGIN_interface * PLUGIN_init( int ncall )
{
   if( ncall > 0 ) return NULL ;  /* only one interface */

   plint = PLUTO_new_interface( "Nudge Dataset" ,
                                "Move bricks around" ,
                                NULL ,
                                PLUGIN_CALL_IMMEDIATELY ,
                                (cptr_func *)NUD_main  ) ;

   PLUTO_add_hint( plint , "Move bricks around" ) ;

   PLUTO_set_sequence( plint , "A:olddset:nudger" ) ;

   return plint ;
}

/**************************************************************************
  return a label string for 3 floats with 3 suffix characters
--------------------------------------------------------------------------*/

#define EPS 0.005

static char * NUD_threestring( float a,float b,float c,char ca,char cb,char cc )
{
   static char label[64] ;
   if( fabs(a) < EPS ) a = 0.0 ;
   if( fabs(b) < EPS ) b = 0.0 ;
   if( fabs(c) < EPS ) c = 0.0 ;
   sprintf(label,"%6.2f%c %6.2f%c %6.2f%c ", a,ca,b,cb,c,cc ) ;
   return label ;
}

static char * NUD_3string( float a,float b,float c,char ca,char cb,char cc )
{
   static char label[64] ;
   if( fabs(a) < EPS ) a = 0.0 ;
   if( fabs(b) < EPS ) b = 0.0 ;
   if( fabs(c) < EPS ) c = 0.0 ;
   sprintf(label,"%.2f%c %.2f%c %.2f%c", a,ca,b,cb,c,cc ) ;
   return label ;
}

/***************************************************************************
  Will be called from AFNI when user selects from Plugins menu.
****************************************************************************/

/* Interface widgets */

static Widget shell=NULL , rowcol , info_lab , choose_pb ;
static Widget nudge_pb, clear_pb, undo_pb, redo_pb, help_pb, quit_pb, doall_pb, print_pb ;
static MCW_arrowval * roll_av , * pitch_av , * yaw_av ,
                    * dS_av   , * dL_av    , * dP_av  , * brick_av ;
static Widget angle_cum_lab , shift_cum_lab ;

static MCW_arrowval * interp_av , * clip_av ;

/* other data */

static int nudger_open = 0 ;

static MCW_DC * dc ;                   /* display context */
static Three_D_View * im3d ;           /* AFNI controller */
static THD_3dim_dataset * dset ;       /* The dataset!    */
static MCW_idcode         dset_idc ;
static int new_dset = 0 ;              /* Is it new?      */
static int dset_ival = 0 ;             /* Sub-brick index */
static char dset_title[THD_MAX_NAME] ; /* Title string    */

static char * NUD_dummy_av_label[2] = { "[Nothing At All]", "[Nothing At All]" };

static int iha=1,ax1=0,ax2=1,ax3=2,hax1=1,hax2=2,hax3=3,adx=1,ady=2,adz=3 ;

static int undo_nall , undo_nuse , undo_ntop ;
static THD_dmat33 * undo_rmat=NULL ;
static THD_dfvec3 * undo_svec=NULL ;

static THD_dmat33 rmat ; /* current rotation matrix = undo_rmat[undo_nuse-1] */
static THD_dfvec3 svec ; /* current shift vector    = undo_svec[undo_nuse-1] */

#define NRESAM 7
#define NYESNO 2
static char * REG_resam_strings[NRESAM] = {
  "NN" ,  "Linear" ,  "Cubic" ,  "Quintic" ,  "Heptic" ,  "Fourier" ,  "Fourier_nopad" } ;

static char * REG_resam_options[NRESAM] = {
 "-NN" , "-linear" , "-cubic" , "-quintic" , "-heptic" , "-Fourier" , "-Fourier_nopad" } ;

static int REG_resam_ints[NRESAM] = {
  MRI_NN, MRI_LINEAR, MRI_CUBIC, MRI_QUINTIC, MRI_HEPTIC, MRI_FOURIER, MRI_FOURIER_NOPAD } ;

static char * YESNO_strings[NYESNO] = { "No" , "Yes" } ;

MRI_IMAGE * imbase = NULL ;

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

char * NUD_main( PLUGIN_interface * plint )
{
   XmString xstr ;

   /*-- sanity checks --*/

   if( ! IM3D_OPEN(plint->im3d) )
      return " \n AFNI Controller\nnot opened?! \n " ;

   if( nudger_open ){
      if( plint->im3d != im3d ){ /* different controller => close it */
         NUD_quit_CB(NULL,NULL,NULL) ;
      } else {                   /* same controller => just raise up */
         XtMapWidget(shell) ;
         XRaiseWindow( XtDisplay(shell) , XtWindow(shell) ) ;
         return NULL ;
      }
   }

   im3d = plint->im3d ;  /* save for local use */

   /*-- create widgets, first time through --*/

   if( shell == NULL ){
      dc = im3d->dc ;        /* save this too */
      NUD_make_widgets() ;
      PLUTO_set_topshell( plint , shell ) ;  /* 22 Sep 2000 */
      RWC_visibilize_widget( shell ) ;       /* 27 Sep 2000 */
   }

   /*-- set titlebar --*/

   { char ttl[PLUGIN_STRING_SIZE] ;
     sprintf(ttl , "AFNI Nudger %s" , AFNI_controller_label(im3d) ) ;
     XtVaSetValues( shell , XmNtitle , ttl , NULL ) ;
   }

   /*-- set the info label --*/

   xstr = XmStringCreateLtoR( "[No dataset]" ,
                              XmFONTLIST_DEFAULT_TAG ) ;
   XtVaSetValues( info_lab , XmNlabelString , xstr , NULL ) ;
   XmStringFree(xstr) ;

   /*-- pop the widget up --*/

   XtMapWidget(shell) ;
   PLUTO_cursorize(shell) ;

   /*-- misc initialization --*/

   dset = NULL ;           /* not editing anything */
   ZERO_IDCODE(dset_idc) ;
   dset_ival = 0 ; AV_assign_ival(brick_av,0) ;
   if( imbase != NULL ){ mri_free(imbase); imbase = NULL; }

   nudger_open = 1 ;      /* editor is now open for business */

   SENSITIZE(nudge_pb ,0) ;
   SENSITIZE(undo_pb  ,0) ;
   SENSITIZE(redo_pb  ,0) ;
   SENSITIZE(doall_pb ,0) ;

   SENSITIZE(choose_pb,1) ; AV_SENSITIZE(brick_av,0) ;

   /* initialize nudgerosity */

   NUD_clear_CB(NULL,NULL,NULL) ;

   LOAD_DIAG_DMAT(rmat,1.0,1.0,1.0) ;
   LOAD_DFVEC3(svec,0.0,0.0,0.0)    ;
   NUD_setcumlab() ;

   /* initialize undo stack */

   if( undo_rmat != NULL ){ free(undo_rmat); undo_rmat = NULL; }
   if( undo_svec != NULL ){ free(undo_svec); undo_svec = NULL; }
   undo_nuse = 0 ;
   undo_ntop = 0 ;
   undo_nall = 1 ;
   undo_rmat = (THD_dmat33 *) malloc(sizeof(THD_dmat33)) ;
   undo_svec = (THD_dfvec3 *) malloc(sizeof(THD_dfvec3)) ;
   NUD_undopush() ;  /* top of stack = current transformation */

   return NULL ;
}

/*------------------------------------------------------------------------
  Make the control popup for this thing
--------------------------------------------------------------------------*/

#define SEP_HOR(ww)  XtVaCreateManagedWidget(                     \
                       "AFNI" , xmSeparatorWidgetClass , (ww) ,   \
                          XmNseparatorType , XmSINGLE_LINE ,      \
                          XmNinitialResourcesPersistent , False , \
                       NULL )

#define SEP_VER(ww) XtVaCreateManagedWidget(                      \
                       "AFNI" , xmSeparatorWidgetClass , (ww) ,   \
                          XmNseparatorType , XmDOUBLE_LINE ,      \
                          XmNorientation   , XmVERTICAL ,         \
                          XmNinitialResourcesPersistent , False , \
                       NULL )

/*-- structures defining action buttons (at bottom of popup) --*/

#define NACT 8  /* number of action buttons */

static MCW_action_item NUD_actor[NACT] = {
 {"Nudge",NUD_nudge_CB,NULL,
  "Applies Angles and Shifts\nto chosen Brick of dataset","Apply Angles/Shifts",1} ,

 {"Clear",NUD_clear_CB,NULL,
  "Clears Angles and\nShifts entry fields","Clear Angles/Shifts",0} ,

 {"Undo",NUD_undo_CB,NULL,
  "Undoes previous nudge, if possible","Undo last nudge",0} ,

 {"Redo",NUD_redo_CB,NULL,
  "Redoes previously undone nudge","Redo last undone nudge",0} ,

 {"Help",NUD_help_CB,NULL,
  "Displays more help" , "Displays more help",0} ,

 {"Quit",NUD_quit_CB,NULL,
  "Discard nudges since last\n'Do All' and close down",
  "Discard nudges and close",0} ,

 {"Do All",NUD_doall_CB,NULL,
  "Apply Angles and Shifts to all sub-\nbricks and save dataset to disk" ,
  "Apply Angles/Shifts; write to disk" , 1 } ,

 {"Print",NUD_print_CB,NULL,
  "Print current Angles and Shifts as\na '3drotate' command, to stderr" ,
  "Print 3drotate command to screen" , 0 }
} ;

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

static void NUD_make_widgets(void)
{
   XmString xstr ;
   Widget hrc ;

   /*** top level shell for window manager ***/

   shell =
      XtVaAppCreateShell(
           "AFNI" , "AFNI" , topLevelShellWidgetClass , dc->display ,

           XmNtitle             , "AFNI Nudger" , /* top of window */
           XmNiconName          , "Nudger"      , /* label on icon */
           XmNdeleteResponse    , XmDO_NOTHING  , /* deletion handled below */
           XmNallowShellResize  , True ,          /* let code resize shell? */
           XmNmappedWhenManaged , False ,         /* must map it manually */
           XmNinitialResourcesPersistent , False ,
      NULL ) ;

   DC_yokify( shell , dc ) ; /* 14 Sep 1998 */

#ifndef DONT_INSTALL_ICONS
   if( afni48_good )             /* set icon pixmap */
      XtVaSetValues( shell ,
                        XmNiconPixmap , afni48_pixmap ,
                     NULL ) ;
#endif

   if( MCW_isitmwm(shell) )      /* remove some MWM functions */
      XtVaSetValues( shell ,
                       XmNmwmFunctions ,
                       MWM_FUNC_MOVE | MWM_FUNC_CLOSE | MWM_FUNC_MINIMIZE ,
                     NULL ) ;

   XmAddWMProtocolCallback(      /* make "Close" window menu work */
           shell ,
           XmInternAtom( dc->display , "WM_DELETE_WINDOW" , False ) ,
           NUD_quit_CB , (XtPointer) plint ) ;

   /*** rowcolumn widget to hold all user interface stuff ***/

   rowcol = XtVaCreateWidget(
             "AFNI" , xmRowColumnWidgetClass , shell ,
                XmNpacking     , XmPACK_TIGHT ,
                XmNorientation , XmVERTICAL ,
                XmNtraversalOn , True  ,
                XmNinitialResourcesPersistent , False ,
             NULL ) ;

   /*** label at top to let user know who we are ***/

   xstr = XmStringCreateLtoR( "[No dataset]" ,
                              XmFONTLIST_DEFAULT_TAG ) ;
   info_lab = XtVaCreateManagedWidget(
                 "AFNI" , xmLabelWidgetClass , rowcol ,
                    XmNlabelString , xstr ,
                    XmNinitialResourcesPersistent , False ,
                 NULL ) ;
   XmStringFree(xstr) ;
   MCW_register_help( info_lab , "Shows dataset being nudged" ) ;
   MCW_register_hint( info_lab , "Shows dataset being nudged" ) ;

   /***** top row of widgets to choose dataset and sub-brick *****/

   SEP_HOR(rowcol) ;

   hrc =  XtVaCreateWidget(
           "AFNI" , xmRowColumnWidgetClass , rowcol ,
              XmNorientation  , XmHORIZONTAL ,
              XmNpacking      , XmPACK_TIGHT ,
              XmNadjustLast   , False ,
              XmNadjustMargin , False ,
              XmNtraversalOn  , True  ,
              XmNmarginWidth  , 0 ,
              XmNmarginHeight , 0 ,
              XmNinitialResourcesPersistent , False ,
           NULL ) ;

   /*** button to let user choose dataset to edit ***/

   xstr = XmStringCreateLtoR( "Choose Dataset" , XmFONTLIST_DEFAULT_TAG ) ;
   choose_pb = XtVaCreateManagedWidget(
                  "AFNI" , xmPushButtonWidgetClass , hrc ,
                     XmNlabelString , xstr ,
                     XmNtraversalOn , True  ,
                     XmNinitialResourcesPersistent , False ,
                  NULL ) ;
   XmStringFree(xstr) ;
   XtAddCallback( choose_pb, XmNactivateCallback, NUD_choose_CB, NULL ) ;
   MCW_register_help( choose_pb ,
                      "Use this to popup a\n"
                      "'chooser' that lets\n"
                      "you select which\n"
                      "dataset to nudge."
                    ) ;
   MCW_register_hint( choose_pb , "Popup dataset chooser" ) ;

   /*** menu to let user choose sub-brick to deal with ***/

   SEP_VER(hrc) ;

   brick_av = new_MCW_arrowval(
                          hrc ,                   /* parent Widget */
                          "Brick" ,               /* label */
                          MCW_AV_optmenu ,        /* option menu style */
                          0 ,                     /* first option */
                          1 ,                     /* last option */
                          0 ,                     /* initial selection */
                          MCW_AV_readtext ,       /* ignored but needed */
                          0 ,                     /* decimal shift */
              (gen_func *)NUD_brick_av_CB ,       /* callback when changed */
                          NULL ,                  /* data for above */
              (str_func *)MCW_av_substring_CB ,   /* text creation routine */
                          NUD_dummy_av_label      /* data for above */
                        ) ;
   MCW_reghelp_children( brick_av->wrowcol ,
                         "Choose the sub-brick\n"
                         "to nudge interactively;\n"
                         "you should also be\n"
                         "viewing this sub-brick" ) ;
   MCW_reghint_children( brick_av->wrowcol , "Sub-brick to nudge" ) ;

   /** some miscellaneous controls **/

   SEP_VER(hrc) ;

   interp_av = new_MCW_arrowval(
                        hrc ,                   /* parent Widget */
                        "Resampling" ,          /* label */
                        MCW_AV_optmenu ,        /* option menu style */
                        0 ,                     /* first option */
                        NRESAM-1 ,              /* last option */
                        3 ,                     /* initial selection */
                        MCW_AV_readtext ,       /* ignored but needed */
                        0 ,                     /* decimal shift */
                        NULL ,                  /* callback when changed */
                        NULL ,                  /* data for above */
            (str_func *)MCW_av_substring_CB ,   /* text creation routine */
                        REG_resam_strings       /* data for above */
                      ) ;
   MCW_reghint_children( interp_av->wrowcol , "Set interpolation method" ) ;

   SEP_VER(hrc) ;

   clip_av = new_MCW_arrowval(
                        hrc ,                   /* parent Widget */
                        "Clip" ,                /* label */
                        MCW_AV_optmenu ,        /* option menu style */
                        0 ,                     /* first option */
                        NYESNO-1 ,              /* last option */
                        1 ,                     /* initial selection */
                        MCW_AV_readtext ,       /* ignored but needed */
                        0 ,                     /* decimal shift */
                        NULL ,                  /* callback when changed */
                        NULL ,                  /* data for above */
            (str_func *)MCW_av_substring_CB ,   /* text creation routine */
                        YESNO_strings           /* data for above */
                      ) ;
   MCW_reghint_children( clip_av->wrowcol , "Clip after interpolation?" ) ;

   XtManageChild(hrc) ;

   /********** Angle choosers ***********/

   SEP_HOR(rowcol) ;

   hrc =  XtVaCreateWidget(
           "AFNI" , xmRowColumnWidgetClass , rowcol ,
              XmNorientation  , XmHORIZONTAL ,
              XmNpacking      , XmPACK_TIGHT ,
              XmNadjustLast   , False ,
              XmNadjustMargin , False ,
              XmNtraversalOn  , True  ,
              XmNmarginWidth  , 0 ,
              XmNmarginHeight , 0 ,
              XmNinitialResourcesPersistent , False ,
           NULL ) ;

   xstr = XmStringCreateLtoR( "Angles: " ,
                              XmFONTLIST_DEFAULT_TAG ) ;
   (void) XtVaCreateManagedWidget(
                 "AFNI" , xmLabelWidgetClass , hrc ,
                    XmNlabelString , xstr ,
                    XmNinitialResourcesPersistent , False ,
                 NULL ) ;
   XmStringFree(xstr) ;

   /** the actual angles **/

   roll_av = new_MCW_arrowval( hrc, "I" ,
                                MCW_AV_downup , -300,300,0 ,
                                MCW_AV_editext , 1 ,
                                NULL , NULL , NULL,NULL ) ;
   MCW_reghint_children( roll_av->wrowcol , "Roll angle [I-axis]" ) ;
   XtVaSetValues( roll_av->wtext , XmNcolumns , 6 , NULL ) ;
   SEP_VER(hrc) ;

   pitch_av = new_MCW_arrowval( hrc, "R" ,
                                MCW_AV_downup , -300,300,0 ,
                                MCW_AV_editext , 1 ,
                                NULL , NULL , NULL,NULL ) ;
   MCW_reghint_children( pitch_av->wrowcol , "Pitch angle [R-axis]" ) ;
   XtVaSetValues( pitch_av->wtext , XmNcolumns , 6 , NULL ) ;
   SEP_VER(hrc) ;

   yaw_av = new_MCW_arrowval( hrc, "A" ,
                                MCW_AV_downup , -300,300,0 ,
                                MCW_AV_editext , 1 ,
                                NULL , NULL , NULL,NULL ) ;
   MCW_reghint_children( yaw_av->wrowcol , "Yaw angle [A-axis]" ) ;
   XtVaSetValues( yaw_av->wtext , XmNcolumns , 6 , NULL ) ;
   SEP_VER(hrc) ;

   /** cumulative label **/

   xstr = XmStringCreateLtoR( "--" , XmFONTLIST_DEFAULT_TAG ) ;
   angle_cum_lab = XtVaCreateManagedWidget(
                     "AFNI" , xmLabelWidgetClass , hrc ,
                        XmNlabelString , xstr ,
                        XmNalignment , XmALIGNMENT_CENTER ,
                        XmNinitialResourcesPersistent , False ,
                        XmNrecomputeSize , True ,
#if 0
                        XmNmarginHeight  , 0 ,
                        XmNmarginBottom  , 0 ,
                        XmNmarginLeft    , 0 ,
                        XmNmarginRight   , 0 ,
                        XmNmarginTop     , 0 ,
                        XmNmarginWidth   , 0 ,
#endif
                        XmNtraversalOn , True  ,
                     NULL ) ;
   XmStringFree(xstr) ;
   MCW_register_hint( angle_cum_lab , "Cumulative [degrees]" ) ;

   XtManageChild(hrc) ;

   /********** Shift choosers ***********/

   /*** SEP_HOR(rowcol) ; ***/

   hrc =  XtVaCreateWidget(
           "AFNI" , xmRowColumnWidgetClass , rowcol ,
              XmNorientation  , XmHORIZONTAL ,
              XmNpacking      , XmPACK_TIGHT ,
              XmNadjustLast   , False ,
              XmNadjustMargin , False ,
              XmNtraversalOn  , True  ,
              XmNmarginWidth  , 0 ,
              XmNmarginHeight , 0 ,
              XmNinitialResourcesPersistent , False ,
           NULL ) ;

   xstr = XmStringCreateLtoR( "Shifts: " ,
                              XmFONTLIST_DEFAULT_TAG ) ;
   (void) XtVaCreateManagedWidget(
                 "AFNI" , xmLabelWidgetClass , hrc ,
                    XmNlabelString , xstr ,
                    XmNinitialResourcesPersistent , False ,
                 NULL ) ;
   XmStringFree(xstr) ;

   /** the actual shifts **/

   dS_av = new_MCW_arrowval( hrc, "S" ,
                                MCW_AV_downup , -999,999,0 ,
                                MCW_AV_editext , 1 ,
                                NULL , NULL , NULL,NULL ) ;
   MCW_reghint_children( dS_av->wrowcol , "Delta Superior" ) ;
   XtVaSetValues( dS_av->wtext , XmNcolumns , 6 , NULL ) ;
   SEP_VER(hrc) ;

   dL_av = new_MCW_arrowval( hrc, "L" ,
                                MCW_AV_downup , -999,999,0 ,
                                MCW_AV_editext , 1 ,
                                NULL , NULL , NULL,NULL ) ;
   MCW_reghint_children( dL_av->wrowcol , "Delta Left" ) ;
   XtVaSetValues( dL_av->wtext , XmNcolumns , 6 , NULL ) ;
   SEP_VER(hrc) ;

   dP_av = new_MCW_arrowval( hrc, "P" ,
                                MCW_AV_downup , -999,999,0 ,
                                MCW_AV_editext , 1 ,
                                NULL , NULL , NULL,NULL ) ;
   MCW_reghint_children( dP_av->wrowcol , "Delta Posterior" ) ;
   XtVaSetValues( dP_av->wtext , XmNcolumns , 6 , NULL ) ;
   SEP_VER(hrc) ;

   /** cumulative label **/

   xstr = XmStringCreateLtoR( "--" , XmFONTLIST_DEFAULT_TAG ) ;
   shift_cum_lab = XtVaCreateManagedWidget(
                     "AFNI" , xmLabelWidgetClass , hrc ,
                        XmNalignment , XmALIGNMENT_BEGINNING ,
                        XmNlabelString , xstr ,
                        XmNinitialResourcesPersistent , False ,
                        XmNrecomputeSize , True ,
#if 0
                        XmNmarginHeight  , 0 ,
                        XmNmarginHeight  , 0 ,
                        XmNmarginBottom  , 0 ,
                        XmNmarginLeft    , 0 ,
                        XmNmarginRight   , 0 ,
                        XmNmarginTop     , 0 ,
                        XmNmarginWidth   , 0 ,
#endif
                        XmNtraversalOn , True  ,
                     NULL ) ;
   XmStringFree(xstr) ;
   MCW_register_hint( shift_cum_lab , "Cumulative [mm]" ) ;

   XtManageChild(hrc) ;

   /*** a set of action buttons below the line ***/

   SEP_HOR(rowcol) ;

   (void) MCW_action_area( rowcol , NUD_actor , NACT ) ;

   nudge_pb = (Widget) NUD_actor[0].data ;
   clear_pb = (Widget) NUD_actor[1].data ;
   undo_pb  = (Widget) NUD_actor[2].data ;
   redo_pb  = (Widget) NUD_actor[3].data ;
   help_pb  = (Widget) NUD_actor[4].data ;
   quit_pb  = (Widget) NUD_actor[5].data ;
   doall_pb = (Widget) NUD_actor[6].data ;
   print_pb = (Widget) NUD_actor[7].data ;

   /*** that's all ***/

   XtManageChild(rowcol) ;
   XtRealizeWidget(shell) ; NI_sleep(1) ; /* will not be mapped */
   return ;
}

/*--------------------------------------------------------------------------
   Compute a rotation matrix specified by 3 angles:
      Q = R3 R2 R1, where Ri is rotation about axis axi by angle thi.
   In these routines, the axis codes (ax1,ax2,ax3, hax1,hax2,hax3, and
   adx,ady,adz) are globals, computed when the dataset is loaded.
----------------------------------------------------------------------------*/

static THD_dmat33 rotmatrix( double th1 , double th2 , double th3  )
{
   THD_dmat33 q , p ;

   if( hax1 < 0 ) th1 = -th1 ;
   if( hax2 < 0 ) th2 = -th2 ;
   if( hax3 < 0 ) th3 = -th3 ;

   LOAD_ROT_DMAT( q , th1 , ax1 ) ;
   LOAD_ROT_DMAT( p , th2 , ax2 ) ; q = DMAT_MUL( p , q ) ;
   LOAD_ROT_DMAT( p , th3 , ax3 ) ; q = DMAT_MUL( p , q ) ;

   return q ;
}

/*-----------------------------------------------------------------------
  Compute the rotation angles from the matrix
  [the signs of these may need to be munged]
-------------------------------------------------------------------------*/

static void rotangles( THD_dmat33 rm, double *th1, double *th2, double *th3 )
{
   *th2 = asin( rm.mat[ax3][ax1] ) ;
   *th1 = atan2( -rm.mat[ax3][ax2] , rm.mat[ax3][ax3] ) ;
   *th3 = atan2( -rm.mat[ax2][ax1] , rm.mat[ax1][ax1] ) ;

   if( hax1 < 0 ) *th1 = -(*th1) ;
   if( hax2 < 0 ) *th2 = -(*th2) ;
   if( hax3 < 0 ) *th3 = -(*th3) ;

   return ;
}

/*-----------------------------------------------------------------------
   Compute the shift vector in dataset coordinates from the user inputs.
-------------------------------------------------------------------------*/

static THD_dfvec3 shiftvec( double dx , double dy , double dz )
{
   double qdx=0.0,qdy=0.0,qdz=0.0 ;
   THD_dfvec3 qv ;

   switch( adx ){
      case  1: qdx = -dx ; break ;
      case -1: qdx =  dx ; break ;
      case  2: qdy = -dx ; break ;
      case -2: qdy =  dx ; break ;
      case  3: qdz = -dx ; break ;
      case -3: qdz =  dx ; break ;
   }

   switch( ady ){
      case  1: qdx = -dy ; break ;
      case -1: qdx =  dy ; break ;
      case  2: qdy = -dy ; break ;
      case -2: qdy =  dy ; break ;
      case  3: qdz = -dy ; break ;
      case -3: qdz =  dy ; break ;
   }

   switch( adz ){
      case  1: qdx = -dz ; break ;
      case -1: qdx =  dz ; break ;
      case  2: qdy = -dz ; break ;
      case -2: qdy =  dz ; break ;
      case  3: qdz = -dz ; break ;
      case -3: qdz =  dz ; break ;
   }

   LOAD_DFVEC3(qv,qdx,qdy,qdz) ; return qv ;
}

/*-----------------------------------------------------------------------
   Compute the user-coordinate shifts from the dataset-coordinate vector
-------------------------------------------------------------------------*/

static void shiftdeltas( THD_dfvec3 sv, double *d1, double *d2, double *d3   )
{
   double qdx,qdy,qdz , dx=0.0,dy=0.0,dz=0.0 ;

   UNLOAD_DFVEC3( sv , qdx,qdy,qdz ) ;

   switch( adx ){
      case  1: dx = -qdx ; break ;
      case -1: dx =  qdx ; break ;
      case  2: dx = -qdy ; break ;
      case -2: dx =  qdy ; break ;
      case  3: dx = -qdz ; break ;
      case -3: dx =  qdz ; break ;
   }

   switch( ady ){
      case  1: dy = -qdx ; break ;
      case -1: dy =  qdx ; break ;
      case  2: dy = -qdy ; break ;
      case -2: dy =  qdy ; break ;
      case  3: dy = -qdz ; break ;
      case -3: dy =  qdz ; break ;
   }

   switch( adz ){
      case  1: dz = -qdx ; break ;
      case -1: dz =  qdx ; break ;
      case  2: dz = -qdy ; break ;
      case -2: dz =  qdy ; break ;
      case  3: dz = -qdz ; break ;
      case -3: dz =  qdz ; break ;
   }

   *d1 = dx ; *d2 = dy ; *d3 = dz ; return ;
}

/*-----------------------------------------------------------------------
   Set the cumulative angles/shifts labels from the current state
   (stored in globals rmat and svec)
-------------------------------------------------------------------------*/

static void NUD_setcumlab(void)
{
   double th1,th2,th3 ;
   XmString xstr ;

   rotangles( rmat, &th1,&th2,&th3 ) ;
   th1 *= iha*(180.0/PI) ; th2 *= iha*(180.0/PI) ; th3 *= iha*(180.0/PI) ;
   xstr = XmStringCreateLtoR( NUD_threestring(th1,th2,th3,'I','R','A') ,
                              XmFONTLIST_DEFAULT_TAG ) ;
   XtVaSetValues( angle_cum_lab , XmNlabelString , xstr , NULL ) ;
   XmStringFree(xstr) ;

   shiftdeltas( svec , &th1,&th2,&th3 ) ;
   xstr = XmStringCreateLtoR( NUD_threestring(th1,th2,th3,'S','L','P') ,
                              XmFONTLIST_DEFAULT_TAG ) ;
   XtVaSetValues( shift_cum_lab , XmNlabelString , xstr , NULL ) ;
   XmStringFree(xstr) ;

   return ;
}

/*-----------------------------------------------------------------------
  Write a 3drotate partial command to the screen corresponding
  to the current nudge -- 31 Aug 2000
-------------------------------------------------------------------------*/

static void NUD_print_CB( Widget w , XtPointer cd , XtPointer cb )
{
   double th1,th2,th3 ;
   char cbuf[256] = "3drotate" ;

   strcat( cbuf , " " ) ;
   strcat( cbuf , REG_resam_options[interp_av->ival] ) ;

   if( clip_av->ival ) strcat( cbuf , " -clipit" ) ;

   rotangles( rmat, &th1,&th2,&th3 ) ;
   th1 *= iha*(180.0/PI) ; th2 *= iha*(180.0/PI) ; th3 *= iha*(180.0/PI) ;
   strcat( cbuf , " -rotate " ) ;
   strcat( cbuf , NUD_3string(th1,th2,th3,'I','R','A') ) ;

   shiftdeltas( svec , &th1,&th2,&th3 ) ;
   strcat( cbuf , " -ashift " ) ;
   strcat( cbuf , NUD_3string(th1,th2,th3,'S','L','P') ) ;

   strcat( cbuf , " -prefix ???  inputdataset" ) ;

   fprintf(stderr,"\nCurrent Nudge command is:\n%s\n",cbuf ) ;
   return ;
}

/*-----------------------------------------------------------------------
   Push the current state (rmat and svec) onto the undo stack
-------------------------------------------------------------------------*/

static void NUD_undopush(void)
{
   if( undo_nuse >= undo_nall ){
      undo_nall = undo_nuse + 4 ;
      undo_rmat = (THD_dmat33 *)realloc( undo_rmat, sizeof(THD_dmat33)*undo_nall );
      undo_svec = (THD_dfvec3 *)realloc( undo_svec, sizeof(THD_dfvec3)*undo_nall );
   }

   undo_rmat[undo_nuse] = rmat ;
   undo_svec[undo_nuse] = svec ;
   undo_nuse++ ; undo_ntop = undo_nuse ; SENSITIZE(redo_pb,0) ;
   return ;
}

/*-------------------------------------------------------------------
  Callback for Nudge button
---------------------------------------------------------------------*/

static void NUD_nudge_CB( Widget w, XtPointer client_data, XtPointer call_data )
{
   float roll,pitch,yaw , dS,dL,dP ;
   THD_dmat33 new_rmat ;
   THD_dfvec3 new_svec , qv ;

   if( dset == NULL ){ XBell(dc->display,100); return; }  /* shouldn't happen */

   roll  = (PI/180.0)*roll_av->fval  ;
   pitch = (PI/180.0)*pitch_av->fval ;
   yaw   = (PI/180.0)*yaw_av->fval   ;
   dS    = dS_av->fval ;
   dL    = dL_av->fval ;
   dP    = dP_av->fval ;

   if( roll==0.0 && pitch==0.0 && yaw==0.0 && dS==0.0 && dL==0.0 && dP==0.0 ){
      (void) MCW_popup_message( nudge_pb ,
                                   " \n"
                                   "** Can't nudge dataset! **\n"
                                   "** All Angle and Shifts **\n"
                                   "** are zero.            **\n" ,
                                MCW_USER_KILL | MCW_TIMER_KILL ) ;
      XBell( dc->display , 100 ) ;
      return ;
   }

   SENSITIZE(undo_pb,1)   ; SENSITIZE(doall_pb,1)    ;
   SENSITIZE(choose_pb,0) ; AV_SENSITIZE(brick_av,0) ;

   new_rmat = rotmatrix( roll, pitch, yaw ) ;
   rmat     = DMAT_MUL( new_rmat , rmat ) ;

   new_svec = DMATVEC( new_rmat , svec )  ;
   qv       = shiftvec( dS , dL , dP ) ;
   svec     = ADD_DFVEC3( new_svec , qv ) ;

   NUD_undopush() ;       /* push new transformatin onto undo list */
   NUD_setcumlab() ;      /* draw the cumulative labels */

   /* actually do something here */

   if( imbase == NULL )                             /* first time: get base */
      imbase = mri_copy( DSET_BRICK(dset,dset_ival) ) ;

   NUD_update_base( nudge_pb ) ; return ;
}

/*-------------------------------------------------------------------
  Callback for Clear button
---------------------------------------------------------------------*/

static void NUD_clear_CB( Widget w, XtPointer client_data, XtPointer call_data )
{
   AV_assign_fval( roll_av  , 0.0 ) ;
   AV_assign_fval( pitch_av , 0.0 ) ;
   AV_assign_fval( yaw_av   , 0.0 ) ;
   AV_assign_fval( dS_av    , 0.0 ) ;
   AV_assign_fval( dL_av    , 0.0 ) ;
   AV_assign_fval( dP_av    , 0.0 ) ;
   return ;
}

/*-------------------------------------------------------------------
  Callback for Undo button
---------------------------------------------------------------------*/

static void NUD_undo_CB( Widget w, XtPointer client_data, XtPointer call_data )
{
   if( undo_nuse <= 1 ){ XBell(dc->display,100); return; }

   undo_nuse-- ;
   rmat = undo_rmat[undo_nuse-1] ;
   svec = undo_svec[undo_nuse-1] ;
   NUD_setcumlab() ;
   if( undo_nuse <= 1 ){ SENSITIZE(undo_pb,0); SENSITIZE(doall_pb,0); }
   SENSITIZE(redo_pb,1) ;

   /* actually do something here */

   NUD_update_base( undo_pb ) ; return ;
}

/*-------------------------------------------------------------------
  Callback for Redo button
---------------------------------------------------------------------*/

static void NUD_redo_CB( Widget w, XtPointer client_data, XtPointer call_data )
{
   if( undo_ntop <= undo_nuse ){ XBell(dc->display,100); return; }

   rmat = undo_rmat[undo_nuse] ;
   svec = undo_svec[undo_nuse] ;
   undo_nuse++ ;
   NUD_setcumlab() ;
   if( undo_nuse >= undo_ntop ) SENSITIZE(redo_pb,0) ;
   SENSITIZE(undo_pb,1) ; SENSITIZE(doall_pb,1) ;

   /* actually do something here */

   NUD_update_base( redo_pb ) ; return ;
}

/*-------------------------------------------------------------------
  Callback for Quit button
---------------------------------------------------------------------*/

static void NUD_quit_CB( Widget w, XtPointer client_data, XtPointer call_data )
{
   if( dset != NULL ){
      DSET_unlock(dset) ; DSET_unload(dset) ; DSET_anyize(dset) ;
      if( undo_nuse > 1 ){                 /* if not at the null nudge */
         MCW_invert_widget(quit_pb) ;
         THD_load_statistics( dset ) ;
         PLUTO_dset_redisplay( dset ) ;
         AFNI_process_drawnotice( im3d ) ;
         MCW_invert_widget(quit_pb) ;
      }
      dset = NULL ;
   }

   if( imbase != NULL ){ mri_free(imbase); imbase = NULL; }

   if( undo_rmat != NULL ){ free(undo_rmat); undo_rmat = NULL; }
   if( undo_svec != NULL ){ free(undo_svec); undo_svec = NULL; }
   undo_nall = undo_nuse = 0 ;

   XtUnmapWidget( shell ) ; nudger_open = 0 ;
   return ;
}

/*-------------------------------------------------------------------
  Callback for help button
---------------------------------------------------------------------*/

static void NUD_help_CB( Widget w, XtPointer client_data, XtPointer call_data )
{
   (void ) new_MCW_textwin( help_pb ,

     "PURPOSE: Nudge a dataset's position a little.\n"
     "\n"
     "CONTROLS:\n"
     "Choose Dataset: button to choose which dataset to move around.\n"
     "Brick:          which single sub-brick of the dataset will be moved,\n"
     "                  prior to use of 'Do All'.\n"
     "Resampling:     choose interpolation method for brick resampling\n"
     "Clip:           clip each brick after interpolation?\n"
     "---------------------------------------------------------------------\n"
     "Angles: the entry fields are the rotational angles to be applied:\n"
     "          positive I = roll  = looking to the left\n"
     "          positive R = pitch = nodding the head forward\n"
     "          positive A = yaw   = tilting left ear towards shoulder\n"
     "Shifts: the entry fields are the translational shifts to be applied.\n"
     "          positive S = superior  = shifting head upwards\n"
     "          positive L = left      = shifting head leftwards\n"
     "          positive P = posterior = shifting head backwards\n"
     "---------------------------------------------------------------------\n"
     "Nudge:  apply the Angles and Shifts entered above to the chosen Brick;\n"
     "          also updates the cumulative angles/shifts\n"
     "Clear:  set the Angles and Shifts to zero\n"
     "Undo:   undo the previous Nudge\n"
     "Redo:   redo the previously undone Nudge\n"
     "Quit:   exit, restoring the dataset to its values stored on disk\n"
     "Do All: apply cumulative angles/shifts to all sub-bricks; write to disk\n"
     "Print:  print (to stderr) 3drotate command equivalent to current nudge\n"
     "        [use 'Print' before 'Do All', since 'Do All' sets nudge to 0]\n"
     "=======================================================================\n"
     "USAGE SUGGESTIONS:\n"
     "* Load the dataset and brick to nudge into this plugin.\n"
     "* Switch the image viewers to the same dataset and brick;\n"
     "    as the brick is nudged, then images will be redrawn.\n"
     "* You can also use the rendering plugin - if DynaDraw is on,\n"
     "    the brick will be re-rendered with each nudge.\n"
     "* If you are comparing the nudged dataset to a reference, and\n"
     "    trying to realign the two, one way is to temporarily make\n"
     "    one of them a fim dataset (using '3drefit -fim'), and then\n"
     "    display it as a color overlay.  When you are happy with\n"
     "    the alignment, you can quit AFNI and use 3drefit to change\n"
     "    the dataset back to whatever it was before.\n"
     "* When using the '-fim' trick on the nudged dataset, you will\n"
     "    have to set the colors and color scaling range appropriately\n"
     "    on the 'Define Function' control panel, otherwise the color\n"
     "    overlay will look so peculiar as to be useless.\n"
     "* Nudge-ing on the single sub-brick is done only in memory, so if\n"
     "    you Quit, the dataset on disk will be unchanged.  When you use\n"
     "    'Do All', all sub-bricks will be nudged the same way and then\n"
     "    be written out to disk, overwriting the original dataset .BRIK.\n"
     "* Instead of using 'Do All', you can use 'Print' to see the 3drotate\n"
     "    parameters to use.  You can then apply these to as many datasets\n"
     "    you want (e.g., in a shell script, to nudge a whole bunch of\n"
     "    datasets exactly the same way).\n"
     "* I suggest you do NOT nudge functional activation maps.  It is better\n"
     "    to nudge the anatomical underlay, or nudge the original EPI time\n"
     "    series.  Nudging a dataset implies interpolating to a new grid,\n"
     "    and this is problematical for the non-smooth activation maps.\n"
     "=======================================================================\n"
     "WARNINGS:\n"
     "* Values past the edge of the dataset are 0, and if they are shifted\n"
     "    into the volume covered by the dataset, you will get 0's there.\n"
     "* Values shifted past the edge of the volume covered by the dataset\n"
     "    will be LOST.  This may seem obvious, but when you are shifting\n"
     "    a functional dataset that is smaller than the anatomical underlay,\n"
     "    it can look mysterious.\n"
     "* One solution to the problem above is to use 3dZeropad to explicitly\n"
     "    put a layer of 0's around the outside of the functional dataset\n"
     "    volume.  Shifted values will then go into this 0 buffer, and\n"
     "    will not be lost.\n"
     "=======================================================================\n"
     "ALGORITHM:\n"
     "* Uses the same basic routines as program 3drotate; see\n"
     "      RW Cox and A Jesmanowicz.\n"
     "      Real-time 3D image registration for functional MRI.\n"
     "      Magnetic Resonance in Medicine, 42: 1014-1018, 1999.\n"
     "    Also see 3drotate.c, 3dvolreg.c, and thd_rot3d.c.\n"
     "* Bricks are not repeatedly interpolated as you nudge - each nudge\n"
     "    takes place using the cumulative angles/shifts starting from the\n"
     "    brick read in from disk.  However, if you re-nudge a dataset\n"
     "    after using 'Do All' to write to disk, you will then be re-\n"
     "    interpolating an already interpolated dataset.\n"
     "* Note that cumulative angles/shifts may not be exactly the sum of\n"
     "    the incremental nudges.  This effect is due to the non-Abelian\n"
     "    nature of 3D rotation (i.e., doing rotation A then B is not the\n"
     "    same as doing rotation B then A).\n"
     "* The angle and shift parameters are specified in the same order\n"
     "    as output by 3dvolreg, and would be input to 3drotate as\n"
     "      -rotate <roll>I <pitch>R <yaw>A  -ashift <dS>S <dL>L <dP>P\n"
     "* Rotations are about the center of the rectangular volume of the\n"
     "    dataset.  This is not likely to be the center of the brain.\n"
     "=======================================================================\n"
     "AUTHOR: RWCox, April 2000\n"
     "=======================================================================\n"

    , TEXT_READONLY ) ;
   return ;
}

/*-------------------------------------------------------------------
  Callback for choose button - give the user some dataset choices.
  Criteria for datasets that can be nudged:
    - must be in current session
    - must have actual bricks
---------------------------------------------------------------------*/

static int                  ndsl = 0 ;
static PLUGIN_dataset_link * dsl = NULL ;

static void NUD_choose_CB( Widget w, XtPointer client_data, XtPointer call_data )
{
   THD_session * ss  = im3d->ss_now ;           /* current session */
   int           vv  = im3d->vinfo->view_type ; /* view type */
   THD_3dim_dataset * qset ;
   int id , ltop , llen ;
   char qnam[THD_MAX_NAME] , label[THD_MAX_NAME] ;
   static char ** strlist = NULL ;

   /* can't do this if a dataset is already active and changed */

   if( dset != NULL && undo_nuse > 1 ){
      (void) MCW_popup_message( choose_pb ,
                                   "Can't change datasets until\n"
                                   "you save the changes you've\n"
                                   "already made.  Or you could\n"
                                   "'Quit' and re-start the Editor" ,
                                MCW_USER_KILL | MCW_TIMER_KILL ) ;
      XBell( dc->display , 100 ) ;
      return ;
   }

   /* initialize */

   ndsl = 0 ;

   /* scan datasets */

   for( id=0 ; id < ss->num_dsset ; id++ ){
      qset = GET_SESSION_DSET(ss,id,vv);
/*      qset = ss->dsset_xform_table[id][vv] ;*/

      if( ! ISVALID_DSET (qset) ) continue ;  /* skip */
      if( ! DSET_INMEMORY(qset) ) continue ;
      if( DSET_BRICK_TYPE(qset,0) == MRI_complex ) continue ;

      ndsl++ ;
      dsl = (PLUGIN_dataset_link *)
              XtRealloc( (char *) dsl , sizeof(PLUGIN_dataset_link)*ndsl ) ;

      make_PLUGIN_dataset_link( qset , dsl + (ndsl-1) ) ;
   }

   /* found nothing? exit */

   if( ndsl < 1 ){
      (void) MCW_popup_message( choose_pb ,
                                   " \nDidn't find any datasets to edit!\n" ,
                                MCW_USER_KILL | MCW_TIMER_KILL ) ;
      XBell( dc->display , 100 ) ;
      return ;
   }

   /*--- 23 Nov 1996: loop over dataset links and patch their titles
                      to include an indicator of the dataset type    ---*/

   ltop = 4 ;
   for( id=0 ; id < ndsl ; id++ ){
      llen = strlen(dsl[id].title) ;
      ltop = MAX(ltop,llen) ;
   }

   for( id=0 ; id < ndsl ; id++ ){
      qset = PLUTO_find_dset( &(dsl[id].idcode) ) ;
      if( ! ISVALID_DSET(qset) ) continue ;
      if( ISANAT(qset) ){
         if( ISANATBUCKET(qset) )         /* 30 Nov 1997 */
            sprintf(qnam,"%-*s [%s:%d]" ,
                    ltop,dsl[id].title ,
                    ANAT_prefixstr[qset->func_type] , DSET_NVALS(qset) ) ;

         else if( DSET_NUM_TIMES(qset) == 1 )
            sprintf(qnam,"%-*s [%s]" ,
                    ltop,dsl[id].title ,
                    ANAT_prefixstr[qset->func_type] ) ;

         else
            sprintf(qnam,"%-*s [%s:3D+t:%d]" ,
                    ltop,dsl[id].title ,
                    ANAT_prefixstr[qset->func_type] , DSET_NUM_TIMES(qset) ) ;

      } else {
         if( ISFUNCBUCKET(qset) )         /* 30 Nov 1997 */
            sprintf(qnam,"%-*s [%s:%d]" ,
                    ltop,dsl[id].title ,
                    FUNC_prefixstr[qset->func_type] , DSET_NVALS(qset) ) ;

         else if( DSET_NUM_TIMES(qset) == 1 )
            sprintf(qnam,"%-*s [%s]" ,
                    ltop,dsl[id].title ,
                    FUNC_prefixstr[qset->func_type] ) ;

         else
            sprintf(qnam,"%-*s [%s:3D+t:%d]" ,
                    ltop,dsl[id].title ,
                    FUNC_prefixstr[qset->func_type] , DSET_NVALS(qset) ) ;
      }

      if( DSET_COMPRESSED(qset) ) strcat(qnam,"z") ;

      strcpy( dsl[id].title , qnam ) ;
   }

   /*--- make a popup chooser for the user to browse ---*/

   POPDOWN_strlist_chooser ;

   strlist = (char **) XtRealloc( (char *)strlist , sizeof(char *)*ndsl ) ;
   for( id=0 ; id < ndsl ; id++ ) strlist[id] = dsl[id].title ;

   sprintf( label , "AFNI Dataset from\nthe %s" , VIEW_typestr[vv] ) ;

   MCW_choose_strlist( w , label , ndsl , -1 , strlist ,
                       (gen_func *)NUD_finalize_dset_CB , NULL     ) ;

   return ;
}

/*---------------------------------------------------------------------------
   Make a label for a sub-brick selector menu
-----------------------------------------------------------------------------*/

static char * NUD_brick_av_label_CB( MCW_arrowval * av , XtPointer cd )
{
   static char blab[32] ;
   THD_3dim_dataset * dset = (THD_3dim_dataset *) cd ;
   static char *lfmt[3] = { "#%1d %-14.14s", "#%2d %-14.14s", "#%3d %-14.14s" };
   static char *rfmt[3] = { "%-14.14s #%1d", "%-14.14s #%2d", "%-14.14s #%3d" };

   if( ISVALID_3DIM_DATASET(dset) ){

#ifdef USE_RIGHT_BUCK_LABELS
      if( DSET_NVALS(dset) < 10 )
        sprintf(blab, rfmt[0] , DSET_BRICK_LABEL(dset,av->ival) , av->ival ) ;
      else if( DSET_NVALS(dset) < 100 )
        sprintf(blab, rfmt[1] , DSET_BRICK_LABEL(dset,av->ival) , av->ival ) ;
      else
        sprintf(blab, rfmt[2] , DSET_BRICK_LABEL(dset,av->ival) , av->ival ) ;
#else
      if( DSET_NVALS(dset) < 10 )
        sprintf(blab, lfmt[0] , av->ival , DSET_BRICK_LABEL(dset,av->ival) ) ;
      else if( DSET_NVALS(dset) < 100 )
        sprintf(blab, lfmt[1] , av->ival , DSET_BRICK_LABEL(dset,av->ival) ) ;
      else
        sprintf(blab, lfmt[2] , av->ival , DSET_BRICK_LABEL(dset,av->ival) ) ;
#endif
   }
   else
      sprintf(blab," #%d ",av->ival) ;  /* should not happen! */

   return blab ;
}

/*------------------------------------------------------------------------------
  Called when the user actually makes the choice of dataset
--------------------------------------------------------------------------------*/

static void NUD_finalize_dset_CB( Widget w, XtPointer fd, MCW_choose_cbs * cbs )
{
   int id = cbs->ival ;
   THD_3dim_dataset * qset ;
   XmString xstr ;
   char str[256] ;

   /* check for errors */

   if( !nudger_open ){ POPDOWN_strlist_chooser; XBell(dc->display,100); return; }

   if( dset != NULL && undo_nuse > 1 ){ XBell(dc->display,100); return; }

   if( id < 0 || id >= ndsl ){ XBell(dc->display,100); return; }

   qset = PLUTO_find_dset( &(dsl[id].idcode) ) ;  /* the new dataset */

   if( qset == NULL ){ XBell(dc->display,100); return; } /* shouldn't happen */

   /* if not same as old dataset, close that one down */

   if( dset != NULL && qset != dset ){
      DSET_unlock(dset) ; DSET_unload(dset) ; DSET_anyize(dset) ;
   }

   /* accept this dataset */

   dset = qset ; dset_idc = dset->idcode ;

   undo_nuse = 1 ;
   undo_ntop = 1 ;
   rmat = undo_rmat[0] ;
   svec = undo_svec[0] ; NUD_setcumlab() ;

   SENSITIZE(undo_pb ,0) ;
   SENSITIZE(redo_pb ,0) ;
   SENSITIZE(nudge_pb,1) ;
   SENSITIZE(doall_pb,0) ;

   /* write the informational label */

   xstr = XmStringCreateLtoR( dsl[id].title , XmFONTLIST_DEFAULT_TAG ) ;
   XtVaSetValues( info_lab , XmNlabelString , xstr , NULL ) ;
   XmStringFree(xstr) ;

   /* lock and load this one into memory (not mmap) */

   DSET_mallocize(dset) ; DSET_lock(dset) ; DSET_load(dset) ;

   if( imbase != NULL ){ mri_free(imbase); imbase = NULL; }

   /* refit the sub-brick selector menu */

   if( dset_ival >= DSET_NVALS(dset) ) dset_ival = DSET_NVALS(dset)-1 ;

   refit_MCW_optmenu( brick_av ,
                      0 ,                       /* new minval */
                      DSET_NVALS(dset)-1 ,      /* new maxval */
                      dset_ival ,               /* new inival */
                      0 ,                       /* new decim? */
          (str_func *)NUD_brick_av_label_CB ,   /* text routine */
                      dset                      /* text data */
                    ) ;

   AV_SENSITIZE( brick_av , (DSET_NVALS(dset) > 1) ) ;

   /* set codes indicating rotation axes:
      iha = left or right handed coordinate order in dataset
      ax1 = axis index for 'I'   hax1 = sign for roll angle
      ax2 = axis index for 'R'   hax2 = sign for pitch angle
      ax3 = axis index for 'A'   hax3 = sign for yaw angle   */

   iha = THD_handedness( dset ) ;
   ax1 = THD_axcode(dset,'I') ; hax1 = ax1 ; ax1 = abs(ax1)-1 ; /* roll */
   ax2 = THD_axcode(dset,'R') ; hax2 = ax2 ; ax2 = abs(ax2)-1 ; /* pitch */
   ax3 = THD_axcode(dset,'A') ; hax3 = ax3 ; ax3 = abs(ax3)-1 ; /* yaw */

   adx = THD_axcode(dset,'S') ;  /* for shifts */
   ady = THD_axcode(dset,'L') ;
   adz = THD_axcode(dset,'P') ;

#if 0
   fprintf(stderr,"NUD_finalize_dset_CB: iha=%d\n"
                  "  ax1 =%2d ax2 =%2d ax3 =%2d\n"
                  "  hax1=%2d hax2=%2d hax3=%2d\n"
                  "  adx =%2d ady =%2d adz =%2d\n" ,
           iha , ax1,ax2,ax3 , hax1,hax2,hax3 , adx,ady,adz ) ;
#endif

   return ;
}

/*-------------------------------------------------------------------
  Callback for sub-brick index arrowval
---------------------------------------------------------------------*/

static void NUD_brick_av_CB( MCW_arrowval * av , XtPointer cd )
{
   if( imbase != NULL ){ XBell(dc->display,100); return; }
   dset_ival = av->ival ;
   return ;
}

/*-------------------------------------------------------------------
   Rotate an image in place according to the current specs
---------------------------------------------------------------------*/

static void NUD_rotate( MRI_IMAGE *im )
{
   int clipit=clip_av->ival , mode=REG_resam_ints[interp_av->ival] ;
   float cbot=0.0,ctop=0.0 ;
   float *fvol ;
   double th1,th2,th3 , dx,dy,dz ;

   if( im == NULL || dset == NULL ) return ;

   rotangles( rmat, &th1,&th2,&th3 ) ;
   if( hax1 < 0 ) th1 = -th1 ;
   if( hax2 < 0 ) th2 = -th2 ;
   if( hax3 < 0 ) th3 = -th3 ;
   UNLOAD_DFVEC3( svec , dx,dy,dz ) ;

#if 0
fprintf(stderr,"th1=%g th2=%g th3=%g\n",th1,th2,th3) ;
#endif

   /* nothing to do? */

   if( fabs(th1) < EPS && fabs(th2) < EPS && fabs(th3) < EPS &&
       fabs(dx)  < EPS && fabs(dy)  < EPS && fabs(dz)  < EPS   ) return ;

#if 0
   if( clipit && (mode == MRI_LINEAR || mode == MRI_NN) ) clipit = 0 ;
#endif

   /*--- 09 May 2005: RGB input image ==> do each channel separately ---*/

#undef  BCLIP
#define BCLIP(x)  if((x)<0.0f)(x)=0.0f; else if((x)>255.0)(x)=255.0

   if( im->kind == MRI_rgb ){
     MRI_IMARR *imtriple ;
     MRI_IMAGE *rim, *gim, *bim, *newim ;
     float *fr, *fg, *fb ;
     register int ii ;

     imtriple = mri_rgb_to_3float( im ) ;
     if( imtriple == NULL ){
       fprintf(stderr,"*** mri_rgb_to_3float fails in NUD_rotate!\n"); return;
     }
     rim = IMAGE_IN_IMARR(imtriple,0) ;
     gim = IMAGE_IN_IMARR(imtriple,1) ;
     bim = IMAGE_IN_IMARR(imtriple,2) ; FREE_IMARR(imtriple) ;
     NUD_rotate(rim); NUD_rotate(gim); NUD_rotate(bim);
     fr = MRI_FLOAT_PTR(rim); fg = MRI_FLOAT_PTR(gim); fb = MRI_FLOAT_PTR(bim);
     for( ii=0 ; ii < im->nvox ; ii++ ){
       BCLIP(fr[ii]) ; BCLIP(fg[ii]) ; BCLIP(fb[ii]) ;
     }
     newim = mri_3to_rgb( rim, gim, bim ) ;
     mri_free(rim) ; mri_free(gim) ; mri_free(bim) ;
     memcpy( MRI_RGB_PTR(im) , MRI_RGB_PTR(newim) , 3*im->nvox ) ;
     mri_free(newim) ;
     return ;
   }

   /*--- need a floating point copy? ---*/

   if( im->kind != MRI_float ){
      fvol = (float *) malloc( sizeof(float) * im->nvox ) ;
      EDIT_coerce_type( im->nvox  ,
                        im->kind  , mri_data_pointer(im) ,
                        MRI_float , fvol ) ;
   } else {
      fvol = MRI_FLOAT_PTR(im) ;
   }

   /* compute bounds? */

   if( clipit ){
      register int ii ; register float bb,tt ;
      bb = tt = fvol[0] ;
      for( ii=1 ; ii < im->nvox ; ii++ ){
              if( fvol[ii] < bb ) bb = fvol[ii] ;
         else if( fvol[ii] > tt ) tt = fvol[ii] ;
      }
      cbot = bb ; ctop = tt ;
   }

   /* actually rotate! */

   THD_rota_method( mode ) ;  /* this line fixed 28 Nov 2000 */

   THD_rota_vol( im->nx , im->ny , im->nz ,
                 fabs(DSET_DX(dset)), fabs(DSET_DY(dset)), fabs(DSET_DZ(dset)),
                 fvol , ax1,th1 , ax2,th2 , ax3,th3 , DELTA_AFTER,dx,dy,dz ) ;

   /* apply bounds? */

   if( clipit ){
     register int ii ; register float bb,tt ;
     bb = cbot ; tt = ctop ;
     for( ii=0 ; ii < im->nvox ; ii++ ){
            if( fvol[ii] < bb ) fvol[ii] = bb ;
       else if( fvol[ii] > tt ) fvol[ii] = tt ;
     }
   }

   /* convert type? */

   if( im->kind != MRI_float ){
      EDIT_coerce_type( im->nvox , MRI_float , fvol ,
                        im->kind  , mri_data_pointer(im) ) ;
      free(fvol) ;
   }

   return ;
}

/*--------------------------------------------------------------------------
  Actually nudge the base brick, and then redisplay it.
----------------------------------------------------------------------------*/

static void NUD_update_base(Widget w)
{
   MRI_IMAGE * im ;

   if( dset == NULL || imbase == NULL || dset_ival >= DSET_NVALS(dset) ) return;

   if( w != NULL ) MCW_invert_widget(w) ;

   im = mri_copy(imbase) ;                                  /* copy base */
   NUD_rotate( im ) ;                                       /* rotate copy */
   EDIT_substitute_brick( dset , dset_ival ,                /* put into dset */
                          im->kind , mri_data_pointer(im) );

   if( ISVALID_STATISTIC(dset->stats) )                     /* 27 Nov 2000 */
      THD_update_statistics( dset ) ;

   mri_clear_data_pointer( im ) ; mri_free(im) ;            /* toss the trash */

   PLUTO_dset_redisplay( dset ) ;                           /* re-show it */
   AFNI_process_drawnotice( im3d ) ;                        /* anyone cares? */
   if( w != NULL ) MCW_invert_widget(w) ;
   return ;
}

/*-------------------------------------------------------------------
  Callback for Do All button - nudge all bricks
---------------------------------------------------------------------*/

static void NUD_doall_CB( Widget w, XtPointer client_data, XtPointer call_data )
{
   int iv , nvals ;
   MRI_IMAGE * im ;
   char str[256] ;
   double th1,th2,th3 ;
   Widget meter=NULL ;

   if( dset == NULL || imbase == NULL || undo_nuse == 1 ){  /* bad bad bad */
      XBell(dc->display,100); return;
   }

   /*----- actually do something -----*/

   /* copy imbase back into dataset */

   EDIT_substitute_brick( dset , dset_ival ,
                          imbase->kind , mri_data_pointer(imbase) ) ;
   mri_clear_data_pointer(imbase) ; mri_free(imbase) ; imbase = NULL ;

   /* nudge each sub-brick */

   nvals = DSET_NVALS(dset) ;
   if( nvals > 1 )
      meter = MCW_popup_meter( shell , METER_TOP_WIDE ) ;

   for( iv=0 ; iv < nvals ; iv++ ){
      MCW_invert_widget(doall_pb) ;

      im = mri_copy( DSET_BRICK(dset,iv) ) ;
      NUD_rotate( im ) ;
      EDIT_substitute_brick( dset , iv ,
                             im->kind , mri_data_pointer(im) ) ;
      mri_clear_data_pointer( im ) ; mri_free(im) ;

      if( nvals > 1 )
         MCW_set_meter( meter , (int)(100.0*(iv+0.5)/nvals) ) ;
   }

   /* store the history of what we just did */

   rotangles( rmat, &th1,&th2,&th3 ) ;
   th1 *= iha*(180.0/PI) ; th2 *= iha*(180.0/PI) ; th3 *= iha*(180.0/PI) ;
   sprintf(str,"plug_nudge: -rotate %s",
           NUD_threestring(th1,th2,th3,'I','R','A') ) ;

   iv = strlen(str) ;
   shiftdeltas( svec , &th1,&th2,&th3 ) ;
   sprintf(str+iv," -ashift %s" ,
           NUD_threestring(th1,th2,th3,'S','L','P') ) ;

   tross_Append_History( dset , str );

   /* write to disk, and redisplay */

   if( nvals > 1 ) MCW_set_meter( meter , 100 ) ;

   DSET_overwrite( dset ) ;
   PLUTO_dset_redisplay( dset ) ;
   AFNI_process_drawnotice( im3d ) ;

   /*----- reset to 0 nudge -----*/

   rmat = undo_rmat[0] ; svec = undo_svec[0] ; NUD_setcumlab() ;

   /* clear undo stack */

   undo_nuse = undo_ntop = 1 ;
   SENSITIZE(undo_pb,0)  ; SENSITIZE(redo_pb,0) ;

   /* can't Do All again right now */

   SENSITIZE(doall_pb,0) ;

   /* allow user to change datasets again */

   SENSITIZE(choose_pb,1) ;
   AV_SENSITIZE( brick_av , (nvals > 1) ) ;

   if( nvals > 1 )
      MCW_popdown_meter(meter) ;
   if( nvals%2 == 1 ) MCW_invert_widget(doall_pb) ;

   return ;
}
