/* ************************************************************************ 
 *         The Amulet User Interface Development Environment              *
 * ************************************************************************
 * This code was written as part of the Amulet project at                 *
 * Carnegie Mellon University, and has been placed in the public          *
 * domain.  If you are using this code or any part of Amulet,             *
 * please contact amulet@cs.cmu.edu to be put on the mailing list.        *
 * ************************************************************************/

/* This file contains member function definitions for the Am_Drawonable_Impl
   object primarily concerned with interaction.
*/

extern "C" {
#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xresource.h>
#include <X11/Xatom.h>
#include <X11/keysym.h>
}

#include <iostream.h>

#include <am_inc.h>

#include GEM__H
#include UNIV_MAP__H

#include "gemX_time.h"
#include GEMX__H

extern Screen_Manager Scrn_Mgr;

bool Am_Main_Loop_Go = true;

// An array of XEvent names used for debugging.  If you have an
// XEvent variable named event_return, do event_names[event_return.type] to
// print the name of it's XEvent type.
// Taken from Xlib Programming Manual Vol. One, p. 256-57
//
static char *event_names[] = {
  "",
  "",
  "KeyPress",
  "KeyRelease",
  "ButtonPress",
  "ButtonRelease",
  "MotionNotify",
  "EnterNotify",
  "LeaveNotify",
  "FocusIn",
  "FocusOut",
  "KeymapNotify",
  "Expose",
  "GraphicsExpose",
  "NoExpose",
  "VisibilityNotify",
  "CreateNotify",
  "DestroyNotify",
  "UnmapNotify",
  "MapNotify",
  "MapRequest",
  "ReparentNotify",
  "ConfigureNotify",
  "ConfigureRequest",
  "GravityNotify",
  "ResizeRequest",
  "CirculateNotify",
  "CirculateRequest",
  "PropertyNotify",
  "SelectionClear",
  "SelectionRequest",
  "SelectionNotify",
  "ColormapNotify",
  "ClientMessage",
  "MappingNotify",
};

Bool is_mapnotify(Display * /* dpy */, XEvent *event_return, XPointer xlib_window) {
  switch (event_return->type) {
  case MapNotify:
    // Got MapNotify event, but if multiple windows have been created and
    // Process_Event has not yet been called, then old MapNotify events may
    // still be in the queue.  Check that we get the one for our window.
    if (event_return->xmap.window == *(Window*)xlib_window) {
      return True;
    }
    break;
  default:
    break;
  }
  return False;
}

// // // // // // // // // // // // // // // // // // // //
// Convert an X input event into a Am_Input_Char
// // // // // // // // // // // // // // // // // // // //

int Am_Double_Click_Time = 250;  // in milleseconds

Am_Click_Count Check_Multi_Click(int code, unsigned int state,
				 Am_Button_Down down,
				 Time time, Screen_Desc* screen) {

    Am_Click_Count result = Am_SINGLE_CLICK;

    if (Am_Double_Click_Time) { // else not interested in multi-click
	// if a down press, then check if double click. If up, then use current
	// down count.  If other mouse event, then ignore multi-click
	if ( down == Am_NEITHER) ; // result is OK, do nothing
	else if ( down == Am_BUTTON_UP) { // use current value
	    
	    if (screen->click_counter >= 7) result = Am_MANY_CLICK;
	    else result = (Am_Click_Count) (screen->click_counter + 1);
		// otherwise, just use single click, so result OK
	}
	else { // is a down press
	    if ( (code  == screen->last_click_code) &&
		 (state == screen->last_click_state) &&
		 ( time <= screen->last_click_time + Am_Double_Click_Time)){ 
		// is multi-click
		++(screen->click_counter);
	
		if (screen->click_counter >= 7) result = Am_MANY_CLICK;
		else result = (Am_Click_Count) (screen->click_counter + 1);
	    }
	    else screen->click_counter = 0;
    
	    // in either case, set up variables for next time
	    screen->last_click_code = code;
	    screen->last_click_state = state;
	    screen->last_click_time = time;

	}
    }
    return result;
}
	

Am_Input_Char create_input_char_from_code (short code,
					   unsigned int state,
					   Am_Button_Down down,
					   Am_Click_Count mouse) {

    bool shft = false;
    bool ctrl = false;
    bool meta = false;

    if ((state & ShiftMask)) shft = true;

    //only use shift lock for alphabetic characters
    if ((state & LockMask) &&
	(code >= 'a') && (code <= 'z')) shft = true;

    if (state & ControlMask) ctrl = true;
    if (state & Mod1Mask) meta = true;

    Am_Input_Char ic = Am_Input_Char(code, shft, ctrl, meta, down, mouse);

    return ic;
}

Am_IMPL_MAP(int2int, int, 0, int, 0)
static Am_Map_int2int *am_key_map = 0;

// returns character code or 0 if modifier or -1 if illegal
short Map_Sym_To_Code(KeySym sym)
{
  short c;
  if (am_key_map == 0) Am_Init_Key_Map();
  c = (short)(am_key_map->GetAt((int) sym));
  if (c) return c;

  if ( (sym >= 65505) && (sym <= 65518) ) {
    // symbol is a modifier key
    c = 0;
  }
  else { // unknown character
    c = -1;
  }
  return c;
}
 
void set_am_key_map (int Sym, int Code)
{
  am_key_map->SetAt(Sym, Code);
}

#if XlibSpecificationRelease < 6
// define XK_* constants that are not defined in keysymdefs.h prior to X11R6
// these values are consistent with those used in Amulet V2
#define XK_Page_Up		0xFF55
#define XK_Page_Down		0xFF56
#define XK_KP_Home		0xFF95
#define XK_KP_Left		0xFF96
#define XK_KP_Up		0xFF97
#define XK_KP_Right		0xFF98
#define XK_KP_Down		0xFF99
#define XK_KP_Prior		0xFF9A
#define XK_KP_Page_Up		0xFF9A
#define XK_KP_Next		0xFF9B
#define XK_KP_Page_Down		0xFF9B
#define XK_KP_End		0xFF9C
#define XK_KP_Begin		0xFF9D
#define XK_KP_Insert		0xFF9E
#define XK_KP_Delete		0xFF9F
#endif

void Am_Init_Key_Map()
{
  // The commented out lines have no Amulet equivalent.  Check there first!
  // Look for these XK_* constants in include/X11/keysymdefs.h

  if (am_key_map == 0) am_key_map = new Am_Map_int2int(153);
  set_am_key_map(XK_BackSpace, Am_BACKSPACE);
  set_am_key_map(XK_Tab, Am_TAB);
  set_am_key_map(XK_Linefeed, Am_LINEFEED);
  set_am_key_map(XK_Clear, Am_CLEAR);
  set_am_key_map(XK_Return, Am_RETURN);
  set_am_key_map(XK_Pause, Am_BREAK);
  set_am_key_map(XK_Scroll_Lock, Am_SCROLL_LOCK);
  set_am_key_map(XK_Mode_switch, Am_ALT_GRAPH);
  //  set_am_key_map(XK_Sys_Req, Am_SYS_REQ);
  set_am_key_map(XK_Escape, Am_ESC);
  set_am_key_map(XK_Delete, Am_DELETE);

  set_am_key_map (XK_Multi_key, Am_COMPOSE_CHARACTER);
  set_am_key_map(XK_Home, Am_HOME);
  set_am_key_map(XK_Left, Am_LEFT_ARROW);
  set_am_key_map(XK_Up, Am_UP_ARROW);
  set_am_key_map(XK_Right, Am_RIGHT_ARROW);
  set_am_key_map(XK_Down, Am_DOWN_ARROW);
  set_am_key_map(XK_Prior, Am_PRIOR);
  set_am_key_map(XK_Page_Up, Am_PAGE_UP);
  set_am_key_map(XK_Next, Am_NEXT);
  set_am_key_map(XK_Page_Down, Am_PAGE_DOWN);
  set_am_key_map(XK_End, Am_END);
  //  set_am_key_map(XK_Begin, Am_BEGIN);
  set_am_key_map(XK_Select, Am_SELECT);
  set_am_key_map(XK_Print, Am_PRINT_KEY);
  //  set_am_key_map(XK_Execute, Am_EXECUTE);
  set_am_key_map(XK_Insert, Am_INSERT);
  set_am_key_map(XK_Undo, Am_UNDO_KEY);
  set_am_key_map(XK_Redo, Am_REDO_KEY);
  set_am_key_map(XK_Menu, Am_MENU);
  set_am_key_map(XK_Find, Am_FIND);
  set_am_key_map(XK_Cancel, Am_CANCEL);
  set_am_key_map(XK_Help, Am_HELP);
  set_am_key_map(XK_Break, Am_BREAK);
  //  set_am_key_map(XK_Mode_switch, );
  //  set_am_key_map(XK_script_switch, );
  set_am_key_map(XK_Num_Lock, Am_NUMLOCK);

  set_am_key_map(XK_KP_Space, Am_SPACE);
  set_am_key_map(XK_KP_Tab, Am_TAB);
  set_am_key_map(XK_KP_Enter, Am_ENTER);
  set_am_key_map(XK_KP_F1, Am_PF1);
  set_am_key_map(XK_KP_F2, Am_PF2);
  set_am_key_map(XK_KP_F3, Am_PF3);
  set_am_key_map(XK_KP_F4, Am_PF4);
  set_am_key_map(XK_KP_Begin, Am_R11);
  set_am_key_map(XK_KP_Home, Am_HOME);
  set_am_key_map(XK_KP_Left, Am_LEFT_ARROW);
  set_am_key_map(XK_KP_Up, Am_UP_ARROW);
  set_am_key_map(XK_KP_Right, Am_RIGHT_ARROW);
  set_am_key_map(XK_KP_Down, Am_DOWN_ARROW);
  set_am_key_map(XK_KP_Prior, Am_PRIOR);
  set_am_key_map(XK_KP_Page_Up, Am_PAGE_UP);
  set_am_key_map(XK_KP_Next, Am_NEXT);
  set_am_key_map(XK_KP_Page_Down, Am_PAGE_DOWN);
  set_am_key_map(XK_KP_End, Am_END);
  //  set_am_key_map(XK_KP_Begin, Am_BEGIN);
  set_am_key_map(XK_KP_Insert, Am_INSERT);
  set_am_key_map(XK_KP_Delete, Am_DELETE_CHAR);
  set_am_key_map(XK_KP_Equal, (short)'=');
  set_am_key_map(XK_KP_Multiply, (short)'*');
  set_am_key_map(XK_KP_Add, (short)'+');
  set_am_key_map(XK_KP_Separator, (short)',');
  set_am_key_map(XK_KP_Subtract, (short)'-');
  set_am_key_map(XK_KP_Decimal, (short)'.');
  set_am_key_map(XK_KP_Divide, (short)'/');
  
  set_am_key_map(XK_KP_0, (short)'0');
  set_am_key_map(XK_KP_1, (short)'1');
  set_am_key_map(XK_KP_2, (short)'2');
  set_am_key_map(XK_KP_3, (short)'3');
  set_am_key_map(XK_KP_4, (short)'4');
  set_am_key_map(XK_KP_5, (short)'5');
  set_am_key_map(XK_KP_6, (short)'6');
  set_am_key_map(XK_KP_7, (short)'7');
  set_am_key_map(XK_KP_8, (short)'8');
  set_am_key_map(XK_KP_9, (short)'9');

  set_am_key_map(XK_F1, Am_F1);
  set_am_key_map(XK_F2, Am_F2);
  set_am_key_map(XK_F3, Am_F3);
  set_am_key_map(XK_F4, Am_F4);
  set_am_key_map(XK_F5, Am_F5);
  set_am_key_map(XK_F6, Am_F6);
  set_am_key_map(XK_F7, Am_F7);
  set_am_key_map(XK_F8, Am_F8);
  set_am_key_map(XK_F9, Am_F9);
  set_am_key_map(XK_F10, Am_F10);

  set_am_key_map(XK_L1, Am_L1);
  set_am_key_map(XK_L2, Am_L2);
  set_am_key_map(XK_L3, Am_L3);
  set_am_key_map(XK_L4, Am_L4);
  set_am_key_map(XK_L5, Am_L5);
  set_am_key_map(XK_L6, Am_L6);
  set_am_key_map(XK_L7, Am_L7);
  set_am_key_map(XK_L8, Am_L8);
  set_am_key_map(XK_L9, Am_L9);
  set_am_key_map(XK_L10, Am_L10);

  set_am_key_map(XK_R1, Am_R1);
  set_am_key_map(XK_R2, Am_R2);
  set_am_key_map(XK_R3, Am_R3);
  set_am_key_map(XK_R4, Am_R4);
  set_am_key_map(XK_R5, Am_R5);
  set_am_key_map(XK_R6, Am_R6);
  set_am_key_map(XK_R7, Am_R7);
  set_am_key_map(XK_R8, Am_R8);
  set_am_key_map(XK_R9, Am_R9);
  set_am_key_map(XK_R10, Am_R10);
  set_am_key_map(XK_R11, Am_R11);
  set_am_key_map(XK_R12, Am_R12);
  set_am_key_map(XK_R13, Am_R13);
  set_am_key_map(XK_R14, Am_R14);
  set_am_key_map(XK_R15, Am_R15);
#ifdef XK_LATIN1 // this should always be defined, but make sure just in case
  set_am_key_map(XK_space, Am_SPACE);
  set_am_key_map(XK_exclam, (short)'!');
  set_am_key_map(XK_quotedbl, (short)'\"');
  set_am_key_map(XK_numbersign, (short)'#');
  set_am_key_map(XK_dollar, (short)'$');
  set_am_key_map(XK_percent, (short)'%');
  set_am_key_map(XK_ampersand, (short)'&');
  set_am_key_map(XK_apostrophe, (short)'\'');
  //  set_am_key_map(XK_quoteright same as apostrophe
  set_am_key_map(XK_parenleft, (short)'(');
  set_am_key_map(XK_parenright, (short)')');
  set_am_key_map(XK_asterisk, (short)'*');
  set_am_key_map(XK_plus, (short)'+');
  set_am_key_map(XK_comma, (short)',');
  set_am_key_map(XK_minus, (short)'-');
  set_am_key_map(XK_period, (short)'.');
  set_am_key_map(XK_slash, (short)'/');
  set_am_key_map(XK_0, (short)'0');
  set_am_key_map(XK_1, (short)'1');
  set_am_key_map(XK_2, (short)'2');
  set_am_key_map(XK_3, (short)'3');
  set_am_key_map(XK_4, (short)'4');
  set_am_key_map(XK_5, (short)'5');
  set_am_key_map(XK_6, (short)'6');
  set_am_key_map(XK_7, (short)'7');
  set_am_key_map(XK_8, (short)'8');
  set_am_key_map(XK_9, (short)'9');

  set_am_key_map(XK_colon, (short)':');
  set_am_key_map(XK_semicolon, (short)';');
  set_am_key_map(XK_less, (short)'<');
  set_am_key_map(XK_equal, (short)'=');
  set_am_key_map(XK_greater, (short)'>');
  set_am_key_map(XK_question, (short)'?');
  set_am_key_map(XK_at, (short)'@');
  set_am_key_map(XK_A, (short)'A');
  set_am_key_map(XK_B, (short)'B');
  set_am_key_map(XK_C, (short)'C');
  set_am_key_map(XK_D, (short)'D');
  set_am_key_map(XK_E, (short)'E');
  set_am_key_map(XK_F, (short)'F');
  set_am_key_map(XK_G, (short)'G');
  set_am_key_map(XK_H, (short)'H');
  set_am_key_map(XK_I, (short)'I');
  set_am_key_map(XK_J, (short)'J');
  set_am_key_map(XK_K, (short)'K');
  set_am_key_map(XK_L, (short)'L');
  set_am_key_map(XK_M, (short)'M');
  set_am_key_map(XK_N, (short)'N');
  set_am_key_map(XK_O, (short)'O');
  set_am_key_map(XK_P, (short)'P');
  set_am_key_map(XK_Q, (short)'Q');
  set_am_key_map(XK_R, (short)'R');
  set_am_key_map(XK_S, (short)'S');
  set_am_key_map(XK_T, (short)'T');
  set_am_key_map(XK_U, (short)'U');
  set_am_key_map(XK_V, (short)'V');
  set_am_key_map(XK_W, (short)'W');
  set_am_key_map(XK_X, (short)'X');
  set_am_key_map(XK_Y, (short)'Y');
  set_am_key_map(XK_Z, (short)'Z');

  set_am_key_map(XK_bracketleft, (short)'[');
  set_am_key_map(XK_backslash, (short)'\\');
  set_am_key_map(XK_bracketright, (short)']');
  set_am_key_map(XK_asciicircum, (short)'^');
  set_am_key_map(XK_underscore, (short)'_');
//  set_am_key_map(XK_grave, (short)'');
  set_am_key_map(XK_quoteleft, (short)'`');
  set_am_key_map(XK_a, (short)'a');
  set_am_key_map(XK_b, (short)'b');
  set_am_key_map(XK_c, (short)'c');
  set_am_key_map(XK_d, (short)'d');
  set_am_key_map(XK_e, (short)'e');
  set_am_key_map(XK_f, (short)'f');
  set_am_key_map(XK_g, (short)'g');
  set_am_key_map(XK_h, (short)'h');
  set_am_key_map(XK_i, (short)'i');
  set_am_key_map(XK_j, (short)'j');
  set_am_key_map(XK_k, (short)'k');
  set_am_key_map(XK_l, (short)'l');
  set_am_key_map(XK_m, (short)'m');
  set_am_key_map(XK_n, (short)'n');
  set_am_key_map(XK_o, (short)'o');
  set_am_key_map(XK_p, (short)'p');
  set_am_key_map(XK_q, (short)'q');
  set_am_key_map(XK_r, (short)'r');
  set_am_key_map(XK_s, (short)'s');
  set_am_key_map(XK_t, (short)'t');
  set_am_key_map(XK_u, (short)'u');
  set_am_key_map(XK_v, (short)'v');
  set_am_key_map(XK_w, (short)'w');
  set_am_key_map(XK_x, (short)'x');
  set_am_key_map(XK_y, (short)'y');
  set_am_key_map(XK_z, (short)'z');
  set_am_key_map(XK_braceleft, (short)'{');
  set_am_key_map(XK_bar, (short)'|');
  set_am_key_map(XK_braceright, (short)'}');
  
  set_am_key_map(XK_asciitilde, (short)'~');
#endif

  // The next section is non-ideal.
  // The best solution is to find (create?) #defines for these magic
  // numbers, like the XK_ macros above.
  // I couldn't find any of these keysyms anywhere.  They're all
  // on Sun keyboards.  In the Older Amulet translation, most of these went
  // to L keys, but that's _not_ correct: you should get the keycode you want,
  // instead of translating it to L anything.
  // 5-31-96 af1x

  set_am_key_map (268500736, Am_REMOVE);
  set_am_key_map (268500850, Am_INSERT_CHAR);
  set_am_key_map (268500848, Am_INSERT_LINE);
  set_am_key_map (268500849, Am_DELETE_LINE);
  set_am_key_map (268500851, Am_DELETE_CHAR);
  set_am_key_map (268500847, Am_CLEAR_LINE);
  set_am_key_map (268500845, Am_USER);

  set_am_key_map (268828528, Am_PROPS_KEY);
  set_am_key_map (268828529, Am_FRONT_KEY);
  set_am_key_map (268828530, Am_COPY_KEY);
  set_am_key_map (268828531, Am_OPEN_KEY);
  set_am_key_map (268828532, Am_PASTE_KEY);
  set_am_key_map (268828533, Am_CUT_KEY);
  /*
    // these aren't handled yet: what Am_ consts should we use?
    case 268828535: c = Am_; break; // SunAudioMute
    case 268828536: c = Am_; break; // SunAudioLowerVolume
    case 268828537: c = Am_; break; // SunAudioRaiseVolume
    case 268828534: c = Am_; break; // SunPowerSwitch
    */

}
						    
Am_Input_Char create_input_char_from_key (unsigned int keycode,
					  unsigned int state,
					  Display* disp) {
    int index;
   
    if (state & ShiftMask) index = 1;
    else index = 0;
    
    KeySym sym = XKeycodeToKeysym(disp, keycode, index);

    if ( (sym == NoSymbol) && (index == 1) ) {
	// try again with unshifted index
	// This makes SHIFT-F1 etc. work on Sun
	sym = XKeycodeToKeysym(disp, keycode, 0);
    }
    
    short code;

    if (sym == NoSymbol) code = 0;
    else code = Map_Sym_To_Code(sym);

    if (code == -1) {// try again with unshifted symbol
	// This makes SHIFT-R7 etc. work on Sun
	KeySym second_sym = XKeycodeToKeysym(disp, keycode, 0);
	code = Map_Sym_To_Code(second_sym);
    }
	   
    if (code > 0) {
	// only support keyboard keys going down
	return create_input_char_from_code (code, state, Am_NEITHER,
					     Am_NOT_MOUSE);
    }
    else { // in case is an illegal character or modifier
	if (code < 0)
	    cout << "** Unknown keyboard symbol " << sym << " ignored\n"
		 << flush;
	return Am_Input_Char(); // null means illegal
    }
}

Am_Input_Char create_input_char_from_mouse (unsigned int button,
					    unsigned int state,
					    Am_Button_Down down,
					    Time time, Screen_Desc* screen)
{
    int code = 0;
    if (button == Button1) code = Am_LEFT_MOUSE;
    else if (button == Button2) code = Am_MIDDLE_MOUSE;
    else if (button == Button3) code = Am_RIGHT_MOUSE;
    else {
      cerr << "** Unknown mouse button " << button << "." << endl;
      Am_Error ();
    }

    Am_Click_Count cnt = Check_Multi_Click(code, state, down, time, screen);

    return create_input_char_from_code (code, state, down, cnt);
}

// // // // // // // // // // // // // // // // // // // //
// Main Input Event Handler
// // // // // // // // // // // // // // // // // // // //

void set_input_event (Am_Input_Event *ev, Am_Input_Char ic, int x, int y,
		      unsigned long time, Am_Drawonable *draw) {
    ev->x = x;
    ev->y = y;
    ev->input_char = ic;
    ev->time_stamp = time;
    ev->draw = draw;
    if (Am_Debug_Print_Input_Events)
	cout << "\n<><><><><> " << ic << " x=" << x << " y=" << y <<
	    " time=" << time << " drawonable=" << draw << endl;
}

bool exit_if_stop_char(Am_Input_Char ic)
{ 
  if (ic == Am_Stop_Character) {
//    cerr << "Got stop event: exiting Amulet main loop." << endl;
    Am_Main_Loop_Go = false;
    return true;
  }
  else return false;
}

void handle_selection_request (XEvent& ev, Am_Drawonable_Impl* draw) {
  // someone wants our selection.
  XEvent notify;
  Atom p;
  if (draw->screen->cut_data == NULL)
    p = None;
  else {
    p = ev.xselectionrequest.property;
    XChangeProperty(ev.xselectionrequest.display, //draw->screen->display, 
		    ev.xselectionrequest.requestor,
		    ev.xselectionrequest.property,
		    XA_STRING, 8, PropModeReplace, 
		    (unsigned char*)draw->screen->cut_data, 
		    strlen(draw->screen->cut_data));
  }
  notify.xany.type = SelectionNotify;
  notify.xselection.display = 
    ev.xselectionrequest.display;
  notify.xselection.send_event = True;
  notify.xselection.requestor = 
    ev.xselectionrequest.requestor;
  notify.xselection.selection = 
    ev.xselectionrequest.selection;
  notify.xselection.target = XA_STRING;
  notify.xselection.property = p;
  notify.xselection.time = 
    ev.xselectionrequest.time;
  
  int error = XSendEvent(draw->screen->display, 
			 ev.xselectionrequest.requestor, 
			 True, 0, &notify);
  cerr << "send event result = " << error << endl;
  
  XSync(draw->screen->display, False);
  // I think we're possibly leaving the property we changed hanging.
}

Bool selection_event(Display* /* d */, XEvent *ev, char*)
{
  if ((ev->xany.type == SelectionNotify) || ev->xany.type == SelectionRequest)
    return True;
  else return False;
}

char* Am_Drawonable_Impl::Get_Cut_Buffer()
{
  if (offscreen) return NULL; // not meaningful for offscreen bitmaps

  // To get a selection properly, we need to send off a SelectionRequest event 
  // (this is done with the call to XConvertSelection), and then wait for a
  // SelectionNotify event.  Since we need to finish this routine synchronously
  // instead of waiting for a SelectionNotify event, we'll just wait until
  // any one event comes in, and if it's a SelectionNotify we'll take it
  // and get the selection, otherwise we'll put it back and pretend the request
  // never happened.  This is a gross hack, but it seems to work fairly well
  // when running on my machine, everything locally.  Probably it will fail more
  // if the selection is on a different machine than the Amulet program.

  if (this == screen->root) { // then we're in a root window
    cerr << "** Gem warning: Get_Cut_Buffer() won't work in a root window.\n";
    return NULL;
  }
  // Make an atom to get the selection in.

  // Request the selection from the X server
  XConvertSelection(screen->display, XA_PRIMARY, XA_STRING, 
		    screen->cut_buffer, xlib_drawable, CurrentTime);
  XSync(screen->display, False);
  // Wait for an event, handling selection requests.
  // if it's a selection notify, get the selection
  XEvent ev;
  while (true) {
    while (!XPending (screen->display));
    //    XNextEvent(screen->display, &ev);
    if (XCheckIfEvent(screen->display, &ev, selection_event, NULL) == False
	//	&& XCheckIfEvent(screen->display, &ev, selection_event, NULL) == False)
	) {
	// if we get here, we didn't get any selection notify event back.
	// should we use cut buffer0 here instead?
	cerr << "** missing selection notify event." << endl;
	//	XPutBackEvent(screen->display, &ev); // checkifevent doesn't dequeue nonmatching events
	return NULL;
      }
    if (ev.xany.type == SelectionNotify) break;
    //    if (ev.xany.type == SelectionRequest) {
    Am_Drawonable_Impl *draw =
      Get_Drawable_Backpointer(ev.xany.display,
			       ev.xany.window);
    if (draw) handle_selection_request(ev, draw);
    //    else handle_selection_request(ev, screen->root);
    //    }
  }
  // if we get here, then we have a selection
  if (ev.xselection.property == None)
    { // then there is no selection value, use cut buffer 0 instead.
      cerr << "** No primary selection, using cut buffer." << endl;
      int n;
      char* tempstr = XFetchBytes(screen->display, &n);
      char* str = new char[n+1];
      strcpy (str, tempstr);
      XFree(tempstr);
      return str;
    }
  else { // we have a selection value
    // Get the property's value.  I got this from an email from
    // "S.Ramakrishnan" <ramakris@vtopus.cs.vt.edu> to bam@cs.cmu.edu
    char str[200];
    char *buff;
    int actual_format; 
    unsigned long num_items_ret, rest;
    Atom actual_type;
    long begin = 0;
    for (*str = 0, rest = 1; rest; strcat(str, buff), begin++, XFree(buff)) {
      XGetWindowProperty(screen->display, xlib_drawable, ev.xselection.property,
			 begin, 1, True, AnyPropertyType, &actual_type,
			 &actual_format, &num_items_ret, &rest, (unsigned char**)&buff);
      if (!buff) break;
    }
    buff = new char[strlen(str) + 1];
    strcpy (buff, str);
    return buff;
  }
}

void Am_Handle_Event_Received (XEvent& event_return) {
  Am_Drawonable_Impl *draw =
    Get_Drawable_Backpointer(event_return.xany.display,
			     event_return.xany.window);
    if (!draw) {
      if (Am_Debug_Print_Input_Events)
	  cout << "<> Input ignored because no drawonable\n" << flush;
      return;
    }
  if (draw->ext_handler) {
    //call the external handler IN ADDITION to local handlers
    draw->ext_handler(&event_return);
  }
  
    Am_Input_Event_Handlers *evh = draw->event_handlers;
    Am_Input_Char ic;

    if (!evh) {
	if (Am_Debug_Print_Input_Events)
	    cout << "<> Input ignored for " << draw <<
		" because no Event_Handler\n" << flush;
	return;
    }
    
    switch (event_return.xany.type) {
    case KeyPress:
	ic = create_input_char_from_key (event_return.xkey.keycode,
					 event_return.xkey.state,
					 draw->screen->display);
	if (ic.code != 0) { // then is a legal code
	    if (exit_if_stop_char(ic)) return;
	    set_input_event (Am_Current_Input_Event, ic, event_return.xkey.x,
			 event_return.xkey.y, event_return.xkey.time,
			 draw);
	    draw->event_handlers->Input_Event_Notify(draw,
						     Am_Current_Input_Event);
	}
	break;
    case ButtonPress:
	ic = create_input_char_from_mouse (event_return.xbutton.button,
					   event_return.xbutton.state,
					   Am_BUTTON_DOWN,
					   event_return.xbutton.time,
					   draw->screen);
	if (exit_if_stop_char(ic)) return;
	set_input_event (Am_Current_Input_Event, ic, event_return.xbutton.x,
			 event_return.xbutton.y, event_return.xbutton.time,
			 draw);
	draw->event_handlers->Input_Event_Notify(draw, Am_Current_Input_Event);
	break;
    case ButtonRelease:
	ic = create_input_char_from_mouse (event_return.xbutton.button,
					   event_return.xbutton.state,
					   Am_BUTTON_UP,
					   event_return.xbutton.time,
					   draw->screen);
	if (exit_if_stop_char(ic)) return;
	set_input_event (Am_Current_Input_Event, ic, event_return.xbutton.x,
			 event_return.xbutton.y, event_return.xbutton.time,
			 draw);
	draw->event_handlers->Input_Event_Notify(draw, Am_Current_Input_Event);
	break;
    case MotionNotify:
	ic = create_input_char_from_code (Am_MOUSE_MOVED,
					   event_return.xmotion.state,
					   Am_NEITHER, Am_SINGLE_CLICK);
	set_input_event (Am_Current_Input_Event, ic, event_return.xmotion.x,
			 event_return.xmotion.y, event_return.xmotion.time,
			 draw);
	draw->event_handlers->Input_Event_Notify(draw, Am_Current_Input_Event);
	break;
    case EnterNotify:
	ic = create_input_char_from_code (Am_MOUSE_ENTER_WINDOW,
					   event_return.xcrossing.state,
					   Am_NEITHER, Am_SINGLE_CLICK);
	set_input_event (Am_Current_Input_Event, ic, event_return.xcrossing.x,
			 event_return.xcrossing.y,
			 event_return.xcrossing.time, draw);
	draw->event_handlers->Input_Event_Notify(draw, Am_Current_Input_Event);
	break;
    case LeaveNotify:
	ic = create_input_char_from_code (Am_MOUSE_LEAVE_WINDOW,
					   event_return.xcrossing.state,
					   Am_NEITHER, Am_SINGLE_CLICK);
	set_input_event (Am_Current_Input_Event, ic, event_return.xcrossing.x,
			 event_return.xcrossing.y,
			 event_return.xcrossing.time, draw);
	draw->event_handlers->Input_Event_Notify(draw, Am_Current_Input_Event);
	break;
    case Expose:
	if (Am_Debug_Print_Input_Events)
	    cout << "<> Exposure Event, x=" << event_return.xexpose.x
		 << " y=" << event_return.xexpose.y <<
		" width=" << event_return.xexpose.width <<
		" height=" << event_return.xexpose.height <<
		" drawonable=" << draw << endl;
	draw->event_handlers->Exposure_Notify(draw,
//					      event_return.xexpose.count,
					      event_return.xexpose.x,
					      event_return.xexpose.y,
					      event_return.xexpose.width,
					      event_return.xexpose.height);
	break;
    case DestroyNotify:
	if (Am_Debug_Print_Input_Events)
	    cout << "<> DestroyNotify, drawonable=" << draw << endl;
	draw->event_handlers->Destroy_Notify(draw);
	break;
//// BUG: Not used
// cases UnmapNotify and MapNotify were commented out
// EAB: there does not appear to be a way to distinguish internally-generated
// map/unmap requests from user-generated iconify/deiconify requests
    case UnmapNotify:
	if (Am_Debug_Print_Input_Events)
	    cout << "<> UnmapNotify, drawonable=" << draw << endl;
//	draw->event_handlers->Unmap_Notify(draw);
	if (draw->expect_map_change) {
	  draw->expect_map_change = false;
	} else {
	  draw->iconify_notify(true);
	  draw->event_handlers->Iconify_Notify(draw, true);
	}
	break;
    case MapNotify:
	if (Am_Debug_Print_Input_Events)
	    cout << "<> MapNotify, drawonable=" << draw << endl;
	//	  draw->event_handlers->Map_Notify(draw, false);
	if (draw->expect_map_change) {
	  draw->expect_map_change = false;
	} else {
	  draw->iconify_notify(false);
	  draw->event_handlers->Iconify_Notify(draw, false);
	}
	break;
    case ReparentNotify: {
	if (Am_Debug_Print_Input_Events)
	    cout << "<> ReparentNotify, drawonable=" << draw << endl;
	int left, top, right, bottom, outer_left, outer_top;
	draw->Inquire_Window_Borders (left, top, right, bottom,
				      outer_left, outer_top);
	draw->event_handlers->Frame_Resize_Notify(draw, left, top,
						  right, bottom);
	break;
      }
    case ConfigureNotify: {
	// Configure events may come in for windows that haven't really
	// changed size or position, and in this case the events are ignored
	// (i.e., Gem does not pass them on to the user's event-handler).
	// For example, a 'valid' configure event may be percieved and
	// dispatched that causes the user's ConfigureNotify Event Handler
	// to set the size of the window, which in turn generates a 'bogus'
	// configure event.  When the bogus event is percieved here, we
	// detect it by noticing that its values are equal to those already
	// installed in the drawonable, and throw it away.
	//
        // A Good ConfigureNotify handler should respond to the event
	// not by moving the window again, but by just setting its internal
	// state to correspond to where the window was moved to.  The window
	// manager doesn't have to put the window where you request, or make
	// it the correct size.  It tells you where it put it through a
	// ConfigureNotify event.  If you try to force the WM to put it
	// where you want by looking at the configureNotify event to find
	// out where it actually put it, you might get walking windows
	// no matter what you do.
	//
	// Note: Gem specifications dictate that the "left" and "top" of a
	// window is the left and top of its frame (not necessarily its
	// drawable area).  The "width" and "height" of a window is the
	// width and height of its drawable area.
	// Also, things drawn inside the window have their coordinates
	// relative to the drawable area's left and top.
	//
	int x, y, w, h, left, top, width, height, outer_left, outer_top,
	  lb, tb, rb, bb;

	// We need to use inquire_window_borders to calculate the correct
	// x and y values, because X windows returns local coordinates
	// in the event, not global coordinates, so they're always (0,0).
	
	if (draw->Inquire_Window_Borders (lb, tb, rb, bb,
					  outer_left, outer_top)) {
	  x = outer_left;
	  y = outer_top;
	  w = event_return.xconfigure.width;
	  h = event_return.xconfigure.height;
	  draw->Get_Position (left, top);
	  draw->Get_Size (width, height);
	  //	  cout << "Configure notify: " << (void*)draw <<  " " << w << " " << h << " " << event_return.xany.window << endl;
	  
	  // Only generate a Gem configure-notify event if something changed
	  if ((x != left) || (y != top) || (w != width) || (h != height)) {
	    if (Am_Debug_Print_Input_Events)
	      cout << "<> Configure Notify, x=" << x << " y=" << y <<
		" width=" << width << " height=" << height <<
		" drawonable=" << draw << endl;
	    draw->reconfigure (x, y, w, h);
	    draw->event_handlers->Configure_Notify(draw, x, y, w, h);
	  }
	}
	break;
      }
    case ClientMessage:
      // this is where we get window destroy messages 
      // from the window manager.
      {
	// this is speed-inefficient, but it never happens, so who cares.
	Atom wm_delete_window = XInternAtom(event_return.xclient.display, 
					    "WM_DELETE_WINDOW", False);
	if ((unsigned long) event_return.xclient.data.l[0] != wm_delete_window) break; 
	// not a delete window client message.
	draw->event_handlers->Destroy_Notify(draw);
      }
      break;
    case SelectionClear: 
      // We get this event when we're forced to release our selection.
      // We'll ignore it for now.
      break;
    case SelectionRequest:
      handle_selection_request(event_return, draw);
      break;
      // next all the events we don't expect to occur
    case KeyRelease:
    case FocusIn:
    case FocusOut:
    case KeymapNotify:
    case VisibilityNotify:
    case MapRequest:
    case ConfigureRequest:
    case GravityNotify:
    case ResizeRequest:
    case CirculateNotify:
    case CirculateRequest:
    case PropertyNotify:
    case ColormapNotify:
    case MappingNotify:
	cout << "** Received event of unexpected type: " <<
	    event_names[event_return.type] << endl;
    case CreateNotify:
    case GraphicsExpose:
    case NoExpose:
    case SelectionNotify: // we get this here if get_cut_buffer misses it.
      
	// we do get these events unfortunately, so silently ignore them.
	break;
    } // end switch
}	

Bool is_input_event (XEvent& event) {
  switch (event.xany.type) {
  case ButtonPress:
  case ButtonRelease:
  case KeyPress:
  case MotionNotify:
  case EnterNotify:
  case LeaveNotify:
    return true;
  default:
    return false;
  }
}

/*
 *  Flush_Extra_Move_Events:  If parameter is a mouse move event, then throw
 *				away all other contiguous move events until
 * 				the last one, and leave event_return filled
 *				with the last one.  Disp_ version for multi-
 *				screen, and UseX_ for not
 */
void Disp_Flush_Extra_Move_Events(XEvent& event_return) {
  int cnt = 0;
  if (event_return.xany.type == MotionNotify) {
    XEvent next_event_return;
    while (Scrn_Mgr.Pending (&next_event_return)) {
      if (next_event_return.xany.type == MotionNotify) {
	event_return = next_event_return;
	cnt ++;
      }
      else {
	Scrn_Mgr.Put_Event_Back (next_event_return);
	break;
      }
    }
  }
  if (Am_Debug_Print_Input_Events && cnt > 0)
    cout << "<> Multi Ignoring " << cnt << " move events\n" << flush;
}

void UseX_Flush_Extra_Move_Events(XEvent& event_return) {
  int cnt = 0;
  if (event_return.xany.type == MotionNotify) {
    XEvent next_event_return;
    while (XPending (Main_Display)) {
      XNextEvent (Main_Display, &next_event_return);
      if (next_event_return.xany.type == MotionNotify) {
	event_return = next_event_return;
	cnt ++;
      }
      else {
	XPutBackEvent(Main_Display, &next_event_return);
	break;
      }
    }
  }
  if (Am_Debug_Print_Input_Events && cnt > 0)
    cout << "<> Ignoring " << cnt << " move events\n" << flush;
}

/*
 *  Process_Immediate_Event:  Does not wait for an event, but processes
 *                            the first event in the queue and all non-input
 *                            events after it until an input event is seen.
 *                            The function returns when it encounters an input
 *                            event (excluding the case where the first event
 *                            is an input event) or when the queue is empty.
 */
void Am_Drawonable::Process_Immediate_Event ()
{
  XEvent event_return;
  
  if (More_Than_One_Display) {
    while (Scrn_Mgr.Pending (&event_return)) {
      Disp_Flush_Extra_Move_Events(event_return);
      Am_Handle_Event_Received (event_return);
      // If that was an input event, then process all the remaining
      // non-input events (and don't process another input event).
      if (is_input_event (event_return)) {
        while (Scrn_Mgr.Pending (&event_return)) {
          if (is_input_event (event_return)) {
            Scrn_Mgr.Put_Event_Back (event_return);
            return;
          }
          else
            Am_Handle_Event_Received (event_return);
        }
	return;
      }
    }
  }
  else {
    while (XPending (Main_Display)) {
      XNextEvent (Main_Display, &event_return);
      UseX_Flush_Extra_Move_Events(event_return);
      Am_Handle_Event_Received (event_return);
      // If that was an input event, then process all the remaining
      // non-input events (and don't process another input event).
      if (is_input_event (event_return)) {
        while (XPending (Main_Display)) {
	  XNextEvent (Main_Display, &event_return);
	  if (is_input_event (event_return)) {
	    XPutBackEvent(Main_Display, &event_return);
	    return;
	  }
	  else
	    Am_Handle_Event_Received (event_return);
	}
	return;
      }
    }
  }
}

/*
 *  Process_Event:  waits for the next event, and processes exactly one
 *                  input event and all non-input events before and after
 *                  that input event before returning.  For example
 *                          before            after
 *                       xxxIyyyIzzz   --->   Izzz
 *                  The function returns when it encounters a second input
 *                  event or when the queue is empty.
 */
// Changes for animation interactor, 3-20-96 af1x
// Now Process_Event will time out as soon as deadline passes.
// It will process as few as 0 events, and at maximum, all of the events 
// described above.  It tries hard not to drop events on the floor, by only
// exiting when it's done with event processing.

void Am_Drawonable::Process_Event (const Am_Time& deadline)
{
  XEvent event_return;

  if (!(deadline.Zero()) || More_Than_One_Display) {
    // Figure out when we stop processing
    Am_Time now = Am_Time::Now();
    Am_Time timeout;
    if (deadline > now)
      timeout = deadline - now;
    Am_Time_Data* time_data = Am_Time_Data::Narrow(timeout);

    // Only wait as long as the timeout
    event_return.type = 0;
    Scrn_Mgr.Next_Event (&event_return, time_data->time); 
    time_data->Release();
    // doesn't always return appn event: could time out.
    if (!event_return.type) return;  // we timed out.
    
    Disp_Flush_Extra_Move_Events(event_return); 
    Am_Handle_Event_Received (event_return);

    if (deadline.Is_Past()) return;
    
    // If that was not an input event, then process all the remaining
    // non-input events until we have processed an input event.
    if (!is_input_event (event_return)) {
      while (Scrn_Mgr.Pending (&event_return)) {
	Am_Handle_Event_Received (event_return);
	if (is_input_event (event_return))
	  break;
	if (deadline.Is_Past()) return;
      }
    }
    

    // Process all remaining non-input events
    while (Scrn_Mgr.Pending (&event_return)) {
      if (is_input_event (event_return)) {
	Scrn_Mgr.Put_Event_Back (event_return);
	return;
      }
      else
	Am_Handle_Event_Received (event_return);
      if (deadline.Is_Past()) return;
    }
  }
  else { // single screen, no timeout value.
    XNextEvent (Main_Display, &event_return);
    UseX_Flush_Extra_Move_Events(event_return); 
    Am_Handle_Event_Received (event_return);
    // If that was not an input event, then process all the remaining
    // non-input events until we have processed an input event.
    if (!is_input_event (event_return)) {
      while (XPending (Main_Display)) {
	XNextEvent (Main_Display, &event_return);
	Am_Handle_Event_Received (event_return);
	if (is_input_event (event_return))
	  break;
      }
    }
    // Process all remaining non-input events
    while (XPending (Main_Display)) {
      XNextEvent (Main_Display, &event_return);
      if (is_input_event (event_return)) {
	XPutBackEvent(Main_Display, &event_return);
	return;
      }
      else
	Am_Handle_Event_Received (event_return);
    }
  }
}

/*
 *  Wait_For_Event: waits until the event queue is not empty.  Will return
 *                  immediately if queue is already not empty.  Does not
 *                  process anything
 */
// Note: this is never used anywhere. 3-26-96 af1x
void Am_Drawonable::Wait_For_Event ()
{
  // FIX THIS TO USE AM_TIME
  long timeout_msec = 100;
  timeval timeout;
  timeout.tv_sec = timeout_msec / 1000;
  timeout.tv_usec = (timeout_msec % 1000) * 1000;
  if (More_Than_One_Display)
    Scrn_Mgr.Wait_For_Event (timeout);
  else {
    XEvent event_return;
    XPeekEvent (Main_Display, &event_return);
  }
}

void Am_Drawonable::Main_Loop ()
{
  Am_Time no_timeout;
  while (Am_Main_Loop_Go)
    Process_Event (no_timeout);
}

// // // // // // // // // // // // // // // // // // // //
// Am_Drawonable member functions
// // // // // // // // // // // // // // // // // // // //

void Am_Drawonable_Impl::Initialize_Event_Mask ()
{
    want_enter_leave = false;
    want_multi_window = false;
    want_move = false;
    current_event_mask = ( ButtonPressMask     | ButtonReleaseMask |
			   KeyPressMask        | ExposureMask      |
			   StructureNotifyMask );
    // current_event_mask can be now passed to XCreateWindow as part
    // of the XSetWindowAttributes
}
	
void Am_Drawonable_Impl::set_drawable_event_mask ()
{
  if (want_move) {
    unsigned int pointer_active_mask;

    current_event_mask = // *report-motion-em*
	  ExposureMask | PointerMotionMask |
	  ButtonPressMask | ButtonReleaseMask | 
	  KeyPressMask | StructureNotifyMask;
    pointer_active_mask = // *report-motion-pem*
	  (unsigned int)
	  (ButtonPressMask | ButtonReleaseMask | PointerMotionMask);

    if (want_enter_leave) { // add enter leave masks
      current_event_mask  |= EnterWindowMask | LeaveWindowMask;
      pointer_active_mask |=
	    (unsigned int)(EnterWindowMask | LeaveWindowMask);
    }
    if (want_multi_window) { // add owner-grab-button
      current_event_mask |= OwnerGrabButtonMask;
	  // don't use OwnerGrabButtonMask in the p_a_m mask for grab
    }
      
    // this will change an active grab if one is in process because
    // changing the window's event mask will have no effect if there is
    // an active grab already.  Active grabs happen whenever
    // the mouse button is pressed down.
    XChangeActivePointerGrab (screen->display, pointer_active_mask,
                              None, CurrentTime);
  }
  else {  // don't want motion events, don't need a pointer mask
    current_event_mask = // *ignore-motion-em*
	ExposureMask | ButtonPressMask | ButtonReleaseMask | 
	KeyPressMask | StructureNotifyMask;

    if (want_enter_leave) 
      current_event_mask |= EnterWindowMask | LeaveWindowMask;
    if (want_multi_window) current_event_mask |= OwnerGrabButtonMask;
  }

  if (Am_Debug_Print_Input_Events)
    cout << "Changing Event Mask to " << current_event_mask << " for "
	 << this << endl;

  // now call X to install event mask
  XSelectInput(screen->display, xlib_drawable, current_event_mask);

  // After the XSelectInput, you need to force-output because when
  // using the backgroup m-e-l process, otherwise this doesn't get noticed.
  // *no m-e-l yet* this->Flush_Output();

  // Ignore NoExpose and GraphicsExpose events (double buffering)
  XSetGraphicsExposures(screen->display, screen->gc, false);
}

void Am_Drawonable_Impl::Set_Enter_Leave ( bool want_enter_leave_events )
{
   if (want_enter_leave_events != want_enter_leave) {
	want_enter_leave = want_enter_leave_events;
	this->set_drawable_event_mask ();
    }
}

void Am_Drawonable_Impl::Set_Want_Move ( bool want_move_events )
{
    if (want_move != want_move_events) { // then changing
	want_move = want_move_events;
	this->set_drawable_event_mask ();
    }
}

void Am_Drawonable_Impl::Set_Multi_Window ( bool want_multi )
{
    if (want_multi != want_multi_window) { // then changing
	want_multi_window = want_multi;
	this->set_drawable_event_mask ();
    }
}

void Am_Drawonable_Impl::Discard_Pending_Events()
{
    // * NIY *
    cout << "** Discarding pending input events NIY \n";
}

void Am_Drawonable_Impl::Set_Input_Dispatch_Functions
          (Am_Input_Event_Handlers* evh)
{
  event_handlers = evh;
}

void Am_Drawonable_Impl::Get_Input_Dispatch_Functions
          (Am_Input_Event_Handlers*& evh)
{
  evh = event_handlers;
}

//Find the child-most drawonable at the current cursor position
Am_Drawonable* Am_Drawonable_Impl::Get_Drawonable_At_Cursor() {
  Display* display = screen->display;
  Window parent, root_return, child_return;
  int root_x_return, root_y_return, win_x_return, win_y_return;
  unsigned int mask_return;
  parent = screen->root->xlib_drawable;
  Am_Drawonable_Impl *return_draw = NULL;
  Am_Drawonable_Impl *draw = NULL;
  // need to find leaf-most window that contains the cursor, keep
  // going down until fail
  while(true) {
    if(!XQueryPointer (display, parent, &root_return, &child_return,
		       &root_x_return, &root_y_return, &win_x_return,
		       &win_y_return, &mask_return))
      return NULL; //if XQueryPointer returns false, then not on right screen
    if (child_return) {
      if (child_return == parent) {
	// looping, return last good drawonable
	return return_draw;
      }
      else draw = Get_Drawable_Backpointer(display, child_return);
    }
    else return return_draw; //no child found, return last good drawonable
    if (draw) { //save in case this is the last good one
      return_draw = draw;
    }
    //have a child, loop to see if can find a child of that child
    parent = child_return;
  }
}
 
// This function is called during window creation, and when a window changes
// from invisible to visible.  If you do not wait for the MapNotify event
// before drawing, then your window may come up blank.
//

void wait_for_mapnotify (Display *dpy, Window *xlib_window) {
  XEvent event_return;

  while(1) {
    XPeekIfEvent(dpy, &event_return, is_mapnotify, (XPointer)xlib_window);
    // XPeekIfEvent may have returned without setting event_return (in other
    // words, is_mapnotify returned False).
    if (event_return.type == MapNotify)
      return;
  }
}
