static int GTextFieldDoChange(GTextField *gt, GEvent *event) {
    int ss = gt->sel_start, se = gt->sel_end;
    int pos, l, xpos, sel;
    unichar_t *upt;

    if ( ( event->u.chr.state&(GMenuMask()&~ksm_shift)) ||
	    event->u.chr.chars[0]<' ' || event->u.chr.chars[0]==0x7f ) {
	switch ( event->u.chr.keysym ) {
	  case GK_BackSpace:
	    if ( gt->sel_start==gt->sel_end ) {
		if ( gt->sel_start==0 )
return( 2 );
		--gt->sel_start;
	    }
	    GTextField_Replace(gt,nullstr);
return( true );
	  break;
	  case GK_Delete:
	    if ( gt->sel_start==gt->sel_end ) {
		if ( gt->text[gt->sel_start]==0 )
return( 2 );
		++gt->sel_end;
	    }
	    GTextField_Replace(gt,nullstr);
return( true );
	  break;
	  case GK_Left: case GK_KP_Left:
	    if ( gt->sel_start==gt->sel_end ) {
		gt->sel_start = GTBackPos(gt,gt->sel_start,event->u.chr.state&ksm_meta);
		if ( !(event->u.chr.state&ksm_shift ))
		    gt->sel_end = gt->sel_start;
	    } else if ( event->u.chr.state&ksm_shift ) {
		if ( gt->sel_end==gt->sel_base ) {
		    gt->sel_start = GTBackPos(gt,gt->sel_start,event->u.chr.state&ksm_meta);
		} else {
		    gt->sel_end = GTBackPos(gt,gt->sel_end,event->u.chr.state&ksm_meta);
		}
	    } else {
		gt->sel_end = gt->sel_base = gt->sel_start;
	    }
	    GTextField_Show(gt,gt->sel_start);
return( 2 );
	  break;
	  case GK_Right: case GK_KP_Right:
	    if ( gt->sel_start==gt->sel_end ) {
		gt->sel_end = GTForePos(gt,gt->sel_start,event->u.chr.state&ksm_meta);
		if ( !(event->u.chr.state&ksm_shift ))
		    gt->sel_start = gt->sel_end;
	    } else if ( event->u.chr.state&ksm_shift ) {
		if ( gt->sel_end==gt->sel_base ) {
		    gt->sel_start = GTForePos(gt,gt->sel_start,event->u.chr.state&ksm_meta);
		} else {
		    gt->sel_end = GTForePos(gt,gt->sel_end,event->u.chr.state&ksm_meta);
		}
	    } else {
		gt->sel_start = gt->sel_base = gt->sel_end;
	    }
	    GTextField_Show(gt,gt->sel_start);
return( 2 );
	  break;
	  case GK_Up: case GK_KP_Up:
	    if ( gt->numericfield ) {
		GTextFieldIncrement(gt,(event->u.chr.state&(ksm_shift|ksm_control))?10:1);
return( 2 );
	    }
	    if ( !gt->multi_line )
	  break;
	    if ( !( event->u.chr.state&ksm_shift ) && gt->sel_start!=gt->sel_end )
		gt->sel_end = gt->sel_base = gt->sel_start;
	    else {
		pos = gt->sel_start;
		if ( ( event->u.chr.state&ksm_shift ) && gt->sel_start==gt->sel_base )
		    pos = gt->sel_end;
		l = GTextFieldFindLine(gt,gt->sel_start);
		GRect pos_rect;
		int ll = gt->lines8[l+1]==-1 ? -1 : gt->lines8[l+1]-gt->lines8[l];
		sel = u2utf8_index(gt->sel_start-gt->lines[l],gt->utf8_text+gt->lines8[l]);
		GDrawLayoutInit(gt->g.base,gt->utf8_text+gt->lines8[l],ll,NULL);
		GDrawLayoutIndexToPos(gt->g.base,sel,&pos_rect);
		xpos = pos_rect.x;
		if ( l!=0 ) {
		    GDrawLayoutInit(gt->g.base,gt->utf8_text+gt->lines8[l-1],gt->lines8[l]-gt->lines8[l-1],NULL);
		    pos = GDrawLayoutXYToIndex(gt->g.base,xpos,0);
		    pos = utf82u_index(pos+gt->lines8[l-1],gt->utf8_text);
		}
		if ( event->u.chr.state&ksm_shift ) {
		    if ( pos<gt->sel_base ) {
			gt->sel_start = pos;
			gt->sel_end = gt->sel_base;
		    } else {
			gt->sel_start = gt->sel_base;
			gt->sel_end = pos;
		    }
		} else {
		    gt->sel_start = gt->sel_end = gt->sel_base = pos;
		}
	    }
	    GTextField_Show(gt,gt->sel_start);
return( 2 );
	  break;
	  case GK_Down: case GK_KP_Down:
	    if ( gt->numericfield ) {
		GTextFieldIncrement(gt,(event->u.chr.state&(ksm_shift|ksm_control))?-10:-1);
return( 2 );
	    }
	    if ( !gt->multi_line )
	  break;
	    if ( !( event->u.chr.state&ksm_shift ) && gt->sel_start!=gt->sel_end )
		gt->sel_end = gt->sel_base = gt->sel_end;
	    else {
		pos = gt->sel_start;
		if ( ( event->u.chr.state&ksm_shift ) && gt->sel_start==gt->sel_base )
		    pos = gt->sel_end;
		l = GTextFieldFindLine(gt,gt->sel_start);
		GRect pos_rect;
		int ll = gt->lines8[l+1]==-1 ? -1 : gt->lines8[l+1]-gt->lines8[l];
		sel = u2utf8_index(gt->sel_start-gt->lines[l],gt->utf8_text+gt->lines8[l]);
		GDrawLayoutInit(gt->g.base,gt->utf8_text+gt->lines8[l],ll,NULL);
		GDrawLayoutIndexToPos(gt->g.base,sel,&pos_rect);
		xpos = pos_rect.x;
		if ( l<gt->lcnt-1 ) {
		    ll = gt->lines8[l+2]==-1 ? -1 : gt->lines8[l+2]-gt->lines8[l+1];
		    GDrawLayoutInit(gt->g.base,gt->utf8_text+gt->lines8[l+1],ll,NULL);
		    pos = GDrawLayoutXYToIndex(gt->g.base,xpos,0);
		    pos = utf82u_index(pos+gt->lines8[l+1],gt->utf8_text);
		}
		if ( event->u.chr.state&ksm_shift ) {
		    if ( pos<gt->sel_base ) {
			gt->sel_start = pos;
			gt->sel_end = gt->sel_base;
		    } else {
			gt->sel_start = gt->sel_base;
			gt->sel_end = pos;
		    }
		} else {
		    gt->sel_start = gt->sel_end = gt->sel_base = pos;
		}
	    }
	    GTextField_Show(gt,gt->sel_start);
return( 2 );
	  break;
	  case GK_Home: case GK_Begin: case GK_KP_Home: case GK_KP_Begin:
	    if ( !(event->u.chr.state&ksm_shift) ) {
		gt->sel_start = gt->sel_base = gt->sel_end = 0;
	    } else {
		gt->sel_start = 0; gt->sel_end = gt->sel_base;
	    }
	    GTextField_Show(gt,gt->sel_start);
return( 2 );
	  break;
	  /* Move to eol. (if already at eol, move to next eol) */
	  case 'E': case 'e':
	    if ( !( event->u.chr.state&ksm_control ) )
return( false );
	    upt = gt->text+gt->sel_base;
	    if ( *upt=='\n' )
		++upt;
	    upt = u_strchr(upt,'\n');
	    if ( upt==NULL ) upt=gt->text+u_strlen(gt->text);
	    if ( !(event->u.chr.state&ksm_shift) ) {
		gt->sel_start = gt->sel_base = gt->sel_end =upt-gt->text;
	    } else {
		gt->sel_start = gt->sel_base; gt->sel_end = upt-gt->text;
	    }
	    GTextField_Show(gt,gt->sel_start);
return( 2 );
	  break;
	  case GK_End: case GK_KP_End:
	    if ( !(event->u.chr.state&ksm_shift) ) {
		gt->sel_start = gt->sel_base = gt->sel_end = u_strlen(gt->text);
	    } else {
		gt->sel_start = gt->sel_base; gt->sel_end = u_strlen(gt->text);
	    }
	    GTextField_Show(gt,gt->sel_start);
return( 2 );
	  break;
	  case 'D': case 'd':
	    if ( event->u.chr.state&ksm_control ) {	/* delete word */
		gtextfield_editcmd(&gt->g,ec_deleteword);
		GTextField_Show(gt,gt->sel_start);
return( true );
	    }
	  break;
	  case 'W': case 'w':
	    if ( event->u.chr.state&ksm_control ) {	/* backword */
		gtextfield_editcmd(&gt->g,ec_backword);
		GTextField_Show(gt,gt->sel_start);
return( true );
	    }
	  break;
	  case 'M': case 'm': case 'J': case 'j':
	    if ( !( event->u.chr.state&ksm_control ) )
return( false );
	    /* fall through into return case */
	  case GK_Return: case GK_Linefeed:
	    if ( gt->accepts_returns ) {
		GTextField_Replace(gt,newlinestr);
return( true );
	    }
	  break;
	  case GK_Tab:
	    if ( gt->completionfield && ((GCompletionField *) gt)->completion!=NULL ) {
		GTextFieldComplete(gt,true);
		gt->was_completing = true;
return( 3 );
	    }
	    if ( gt->accepts_tabs ) {
		GTextField_Replace(gt,tabstr);
return( true );
	    }
	  break;
	  default:
	    if ( GMenuIsCommand(event,H_("Select All|Ctl+A")) ) {
		gtextfield_editcmd(&gt->g,ec_selectall);
return( 2 );
	    } else if ( GMenuIsCommand(event,H_("Copy|Ctl+C")) ) {
		gtextfield_editcmd(&gt->g,ec_copy);
	    } else if ( GMenuIsCommand(event,H_("Paste|Ctl+V")) ) {
		gtextfield_editcmd(&gt->g,ec_paste);
		GTextField_Show(gt,gt->sel_start);
return( true );
	    } else if ( GMenuIsCommand(event,H_("Cut|Ctl+X")) ) {
		gtextfield_editcmd(&gt->g,ec_cut);
		GTextField_Show(gt,gt->sel_start);
return( true );
	    } else if ( GMenuIsCommand(event,H_("Undo|Ctl+Z")) ) {
		gtextfield_editcmd(&gt->g,ec_undo);
		GTextField_Show(gt,gt->sel_start);
return( true );
	    } else if ( GMenuIsCommand(event,H_("Save|Ctl+S")) ) {
		GTextFieldSave(gt,true);
return( 2 );
	    } else if ( GMenuIsCommand(event,H_("Import...|Ctl+Shft+I")) ) {
		GTextFieldImport(gt);
return( true );
	    } else
return( false );
	  break;
	}
    } else {
	GTextField_Replace(gt,event->u.chr.chars);
return( 4 /* Do name completion */ );
    }

    if ( gt->sel_start == gt->sel_end )
	gt->sel_base = gt->sel_start;
    if ( ss!=gt->sel_start || se!=gt->sel_end )
	GTextFieldGrabPrimarySelection(gt);
return( false );
}