/*
 *
 *  Copyright (C) 2002 by Massimiliano Ghilardi
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 * 
 */

/*
 *                 WARNING!
 * 
 * this file is `null_c' and is preprocessed by m4 to produce `null_m4.c'
 * 
 * It must be a valid m4 file, and must produce a valid C include file.
 * 
 */


/*
 * We need to write down only overloaded methods.
 */
 
DECL_BEFORE


#define REVALIDATE(call) do { \
    o->Class->Invalidate(o); \
    call; \
    if (o->parent && (o->vflags & ttvisible_vflags_visible)) \
	Expose_ttvisible((ttvisible)o, ttvisible_repaint_args_WHOLE); \
} while (0)

    
/* ttobj */

/* ttobj has no methods. nothing to do */


/* ttobject */
static NEW(ttobject) {
    /*
     * if argument (o) is non-NULL, use it instead of allocating memory.
     * in this case, set o->oflags to ttobject_oflags_static to avoid
     * deallocating it later with TTFreeMem().
     */
    ttuint oflags = (ttopaque)o ? ttobject_oflags_static : 0;
    
    if ((o || (o = (ttobject)TTAllocMem(Class->size)))) {
	if (AssignId((ttclass)Class, (ttobj)o)) {
	    o->Class = Class;
	    o->refcount = ttobject_refcount_alive;
	    o->oflags = oflags;
	    o->native = o->target_private = o->user_data = (ttany)0;
	    o->events_inprogress = o->events_informed = (ttopaque)0;
	    return o;
	}
	if (!(oflags & ttobject_oflags_static))
	    TTFreeMem(o);
    }
    return (ttobject)0;
}
static BUILD(ttobject) {
    /*
     * this is necessary for all derived classes that do not overload Class->Build.
     * It is not actually useful for ttobject.
     */
    return o;
}
static BREAK(ttobject) {
    /*
     * this is necessary for all derived classes that do not overload Class->Break.
     * It is not actually useful for ttobject.
     */
}
static DEL(ttobject) {
    if (o) {
	o->Class->Break(o);
	
	/* ttobject_refcount_alive flag should already be removed */
	if (!o->refcount) {
	    
	    /* drop Id as late as possible (for listeners) */
	    DropId((ttobj)o);
	    
	    if (!(o->oflags & ttobject_oflags_static))
		TTFreeMem(o);
	} else {
	    /*
	     * WARNING: we did not delete o, let's clear
	     * o->events_inprogress so that we can delete it later
	     */
	    o->events_inprogress &= ~ttmask(ttevent_evtype_del);
	}
    }
}


/* ttvector */
static NEW(ttvector) {
    if ((o = NEWSUPER(ttvector))) {
	o->array_size = o->array_n = (ttopaque)0;
	o->array = (ttany *)0;
    }
    return o;
}
static DEL(ttvector) {
    if (o) {
	if (o->array) {
	    TTFreeMem(o->array);
	    o->array = (ttany *)0;
	}
	DELSUPER(ttvector);
    }
}

static ttbyte null_Grow_ttvector(ttvector o, ttopaque delta_n) {
    ttopaque new_size = (o->array_n + delta_n) * 3 / 2;
    ttany * new_array;

    if (new_size < 6)
	new_size = 6;
    if (!(new_array = TTReallocMem(o->array, new_size * sizeof(ttany)))) {
	new_size = o->array_n + delta_n;
	if (!(new_array = TTReallocMem(o->array, new_size * sizeof(ttany))))
	    return TT_FALSE;
    }
    o->array = new_array;
    o->array_size = new_size;
    return TT_TRUE;
}

ttany DEF(GetAt_ttvector)(ttvector o, ttopaque pos) {
    if (TTAssert(o && IS(ttvector,o)) && pos < o->array_n)
	return o->array[pos];
    return (ttany)0;
}
ttbyte DEF(SetAt_ttvector)(ttvector o, ttopaque pos, ttany value) {
    if (TTAssert(o && IS(ttvector,o)) && pos < o->array_n) {
	o->array[pos] = value;
	return TT_TRUE;
    }
    return TT_FALSE;
}

ttbyte DEF(AddY_ttvector)(ttvector o, ttopaque pos, ttopaque value_n, _R _A(_P(2)) ttany *values) {
    
    if (!value_n)
	return TT_TRUE;
    
    if (values && pos <= o->array_n &&
	(o->array_size >= o->array_n + value_n || null_Grow_ttvector(o, value_n))) {

	if (pos < o->array_n)
	    TTMoveMem(o->array + pos, o->array + pos + value_n, (o->array_n - pos) * sizeof(ttany));
	
	TTCopyMem(values, o->array + pos, value_n * sizeof(ttany));
	o->array_n += value_n;
    
	return TT_TRUE;
    }
    return TT_FALSE;
}
ttopaque DEF(ContainsValue_ttvector)(ttvector o, ttopaque pos_start, ttany value) {
    while (pos_start < o->array_n) {
	if (o->array[pos_start] == value)
	    return pos_start;
	pos_start++;
    }
    return (ttopaque)-1;
}
		     
ttbyte DEF(RemoveRange_ttvector)(ttvector o, ttopaque pos, ttopaque pos_n) {
    if (pos + pos_n <= o->array_n) {
	if (pos + pos_n < o->array_n)
	    TTMoveMem(o->array + pos + pos_n, o->array + pos, (o->array_n - pos - pos_n) * sizeof(ttany));
	o->array_n -= pos_n;
    }
    return TT_TRUE;
}
    
/* ttevent */
static NEW(ttevent) {
    if ((o = NEWSUPER(ttevent))) {
	o->component = (ttcomponent)0;
	o->evtype = o->evcode = o->evflags = 0;
	o->prev = o->next = (ttevent)0;
    }
    return o;
}


/* tteventbig */
static NEW(tteventbig) {
    if ((o = NEWSUPER(tteventbig))) {
	o->x = o->y = o->w = o->h = (ttshort)0;
	o->value = o->old_value = (ttany)0;
	o->data = (TT_CONST ttbyte *)0;
	o->data_len = (ttopaque)0;
    }
    return o;
}


/* ttbitmask */
static NEW(ttbitmask) {
    if ((o = NEWSUPER(ttbitmask))) {
	o->mask0 = o->mask_max = o->mask_n = (opaque)0;
	o->mask = (ttany *)0;
    }
    return o;
}

/* tteventmask */
static NEW(tteventmask) {
    if ((o = NEWSUPER(tteventmask))) {
	o->truth_table = 0x80; /* logical AND */
	o->evtype_mask = o->evcode_mask = o->component_mask = (ttbitmask)0;
    }
    return o;
}
static DEL(tteventmask) {
    if (o) {
	if (TTD.InstalledEM == o)
	    TTD.InstalledEM = (tteventmask)0;
	if (TTD.DefaultEM == o)
	    TTD.DefaultEM = (tteventmask)0;

	if (o->evtype_mask) {
	    TDEL(o->evtype_mask);
	    o->evtype_mask = (ttbitmask)0;
	}
	if (o->evcode_mask) {
	    TDEL(o->evcode_mask);
	    o->evcode_mask = (ttbitmask)0;
	}
	if (o->component_mask) {
	    TDEL(o->component_mask);
	    o->component_mask = (ttbitmask)0;
	}
	DELSUPER(tteventmask);
    }
}

/* ttcallback */
static NEW(ttcallback) {
    if ((o = NEWSUPER(ttcallback))) {
	o->lflags = 0;
	o->component = (ttcomponent)0;
	o->prev = o->next = (ttcallback)0;
    }
    return o;
}
static DEL(ttcallback) {
    if (o) {
	if (o->next)
	    o->next->prev = o->prev;
	if (o->prev)
	    o->prev->next = o->next;
	else if (o->component && o->component->callbacks == o)
	    o->component->callbacks = o->next;

	o->prev = o->next = (ttcallback)0;
	
	THW.DeleteCallback(o);

	DELSUPER(ttcallback);
    }
}

static ttcallback DEF(Create_ttcallback)(ttcomponent c) {
    ttcallback o;
    if ((o = TNEW(ttcallback))) {
	if ((o->next = c->callbacks))
	    o->next->prev = o;
	o->component = c;
	c->callbacks = o;
    }
    return o;
}

static TT_CONST struct s_tavl empty_AVL;

/* ttlistener */
static NEW(ttlistener) {
    if ((o = NEWSUPER(ttlistener))) {
	o->AVL = empty_AVL;
	o->prev = o->next = (ttlistener)0;
	o->event = (ttevent)0;
	o->function = (ttany)0;
	o->arg_component_n = o->arg_event_n = (ttuint)-1;
	o->args = (ttvector)0;
	o->event_mask = (tteventmask)0;
	o->inprogress_prev = o->inprogress_next = (ttlistener)0;
    }
    return o;
}
static DEL(ttlistener) {
    if (o) {
	o->Class->Remove(o);
	if (o->event) {
	    TDEL(o->event);
	    o->event = (ttevent)0;
	}
	if (o->args) {
	    TDEL(o->args);
	    o->args = (ttvector)0;
	}
	if (o->event_mask) {
	    TDEL(o->event_mask);
	    o->event_mask = (tteventmask)0;
	}
	DELSUPER(ttlistener);
    }
}

static void DEF(AddTo_ttlistener)(ttlistener c, ttcomponent o) {
    /* implemented in libTT.c */
    AddTo_ttlistener(c, o);
}

static void DEF(Remove_ttlistener)(ttlistener o) {
    /* implemented in libTT.c */
    Remove_ttlistener(o);
}


/* tttimer */
static NEW(tttimer) {
    if ((o = NEWSUPER(tttimer))) {
	o->delay_t = o->delay_f = (ttany)0;
	o->timer_prev = o->timer_next = (tttimer)0;
    }
    return o;
}
static DEL(tttimer) {
    if (o) {
	o->Class->Remove(o);
	DELSUPER(tttimer);
    }
}
static BUILD(tttimer) {
    if (o->lflags & tttimer_lflags_enabled)
	Activate_tttimer(o, TT_TRUE);
    return o;
}
static BREAK(tttimer) {
    if (o->lflags & tttimer_lflags_enabled)
	Activate_tttimer(o, TT_FALSE);
}
static void DEF(SetEnabled_tttimer)(tttimer o, ttbyte enable) {
    if (!enable != !(o->lflags & tttimer_lflags_enabled)) {
	o->lflags ^= tttimer_lflags_enabled;
	Activate_tttimer(o, enable);
    }
}
static ttbyte DEF(IsEnabled_tttimer)(tttimer o) {
    return !!(o->lflags & tttimer_lflags_enabled);
}
static void DEF(AddTo_tttimer)(tttimer o, ttcomponent c) {
    /* prepend */
    if (c && !o->component) {
	if ((o->next = c->timers))
	    c->timers->prev = o;
	c->timers = o;
	o->prev = NULL;
	o->component = c;
    }
}
static void DEF(Remove_tttimer)(tttimer o) {
    ttcomponent c;
    if ((c = o->component)) {
	if (o->prev)
	    o->prev->next = o->next;
	else
	    c->timers = o->next;
	if (o->next)
	    o->next->prev = o->prev;
	o->component = NULL;
    }
}


/* ttdata */
static NEW(ttdata) {
    if ((o = NEWSUPER(ttdata))) {
	o->AVL = empty_AVL;
	o->component = (ttcomponent)0;
	o->key_len = (ttopaque)0;
	o->key = NULL;
	o->data = (ttany)0;
    }
    return o;
}
static DEL(ttdata) {
    if (o) {
	o->Class->Remove(o);
	DELSUPER(ttdata);
    }
}
static void DEF(AddTo_ttdata)(ttdata o, ttcomponent c) {
    /* implemented in libTT.c */
    AddTo_ttdata(o, c, TT_FALSE);
}
static void DEF(Remove_ttdata)(ttdata o) {
    /* implemented in libTT.c */
    Remove_ttdata(o);
}


/* ttcomponent */
static NEW(ttcomponent) {
    if ((o = NEWSUPER(ttcomponent))) {
	o->callbacks = (ttcallback)0;
	o->listeners = (ttlistener)0;
	o->timers = (tttimer)0;
	o->datas = (ttdata)0;
    }
    return o;
}
static DEL(ttcomponent) {
    if (o) {
	if (!(o->events_informed & ttmask(ttevent_evtype_del))) {
	    /* be sure we fire delete event only ONCE! */
	    o->events_informed |= ttmask(ttevent_evtype_del);
	    FireSimpleEvent(o, ttevent_evtype_del);
	}
	
	if (!o->refcount)
	    /* delete listeners and other stuff only if
	     * it's our last chance to do that */
	    DelAllExtras_ttcomponent(o);
	
	DELSUPER(ttcomponent);
    }
}


/* ttvisible */
static NEW(ttvisible) {
    if ((o = NEWSUPER(ttvisible))) {
	o->vflags = ttvisible_vflags_visible;
	o->prev = o->next = o->parent = o->child_first = o->child_last = NULL;
	o->constraint = (ttany)0;
	o->theme = NULL;
	o->repaint = (ttvisible_repaint_fn)0;
    }
    return o;
}
static DEL(ttvisible) {
    ttvisible c;
    if (o) {
	o->Class->Remove(o);
	while ((c = o->child_first))
	    c->Class->Remove(c);
	DELSUPER(ttvisible);
    }
}
static void DEF(AddTo_ttvisible)(ttvisible o, ttvisible parent, ttany constraint) {
    /* append */
    if (parent && !o->parent) {
	if ((o->prev = parent->child_last))
	    parent->child_last->next = o;
	else
	    parent->child_first = o;
	parent->child_last = o;
	o->next = NULL;
	o->parent = parent;

	o->constraint = constraint;
	parent->Class->Validate(parent);
    }
}
static void DEF(Validate_ttvisible)(ttvisible o) {
}
static void DEF(SetVisible_ttvisible)(ttvisible o, byte visible) {
    if (visible)
	o->vflags |= ttvisible_vflags_visible;
    else
	o->vflags &= ~ttvisible_vflags_visible;
}
static void DEF(Remove_ttvisible)(ttvisible o) {
    ttvisible parent;
    if ((parent = o->parent)) {
	if (o->prev)
	    o->prev->next = o->next;
	else
	    parent->child_first = o->next;
	if (o->next)
	    o->next->prev = o->prev;
	else
	    parent->child_last = o->prev;
	o->parent = NULL;
    }
}
static void DEF(Invalidate_ttvisible)(ttvisible o) {
    /* nothing to do here */
}
static ttbyte DEF(SetTheme_ttvisible)(ttvisible o, tttheme theme) {
    tttheme oldtheme = myTheme(o);
    
    o->theme = theme;
    o->Class->Invalidate(o);
    if (theme != oldtheme && o->parent && (o->vflags & ttvisible_vflags_visible))
	Expose_ttvisible(o, ttvisible_repaint_args_WHOLE);

    return TT_TRUE;
}
static void DEF(Draw_ttvisible)(ttvisible o, ttshort x, ttshort y, ttshort w, ttshort h, ttshort pitch,
				TT_CONST ttbyte *asciidata, TT_CONST ttfont *fontdata, TT_CONST ttattr *attrdata) {
    /* do-nothing */
}
static void DEF(BuiltinRepaint_ttvisible)(ttvisible o, ttshort x, ttshort y, ttshort w, ttshort h) {
    /* do-nothing */
}


/* ttlayout */
static NEW(ttlayout) {
    if ((o = NEWSUPER(ttlayout))) {
	o->widget = NULL;
    }
    return o;
}
static DEL(ttlayout) {
    if (o) {
	if (o->widget)
	    o->widget->Class->SetLayout(o->widget, NULL);

	DELSUPER(ttlayout);
    }
}
static void DEF(SetWidget_ttlayout)(ttlayout o, ttwidget w) {
    o->widget = w;
}
static void DEF(DoLayout_ttlayout)(ttlayout o) {
}


/* ttboxlayout */
static NEW(ttboxlayout) {
    if ((o = NEWSUPER(ttboxlayout))) {
	o->orientation = ttboxlayout_orientation_x;
    }
    return o;
}

/* ttborderlayout */



/* ttnative */
static ttnative DEF(GetRoot_ttnative)(void) {
    return Create_ttnative((ttany)0);
}
static ttshort DEF(GetW_ttnative)(ttnative o) {
    return (ttshort)1;
}
static ttshort DEF(GetH_ttnative)(ttnative o) {
    return (ttshort)1;
}


/* ttwidget */
static NEW(ttwidget) {
    if ((o = NEWSUPER(ttwidget))) {
	o->x = o->y = o->xl = o->yl = 0;
	o->layout = NULL;
	o->w = o->h = o->wl = o->hl = 1;
	o->col = myTheme(o)->bg[tttheme_bg_normal];
	o->tooltip = (tttooltip)0;
    }
    return o;
}
static DEL(ttwidget) {
    if (o) {
	if (o->tooltip) {
	    TDEL(o->tooltip);
	    o->tooltip = (tttooltip)0;
	}
	DELSUPER(ttwidget);
    }
}
static void DEF(AddTo_ttwidget)(ttwidget o, ttvisible parent, ttany constraint) {
    if (parent && !o->parent) {
	/* prepend to list, cannot use FNSUPER(ttwidget)->AddTo() */
	if ((o->next = parent->child_first))
	    parent->child_first->prev = (ttvisible)o;
	else
	    parent->child_last = (ttvisible)o;
	parent->child_first = (ttvisible)o;
	o->prev = NULL;
	o->parent = parent;

	o->constraint = constraint;
	parent->Class->Validate(parent);
    }
}
static void DEF(Validate_ttwidget)(ttwidget o) {
}
static void DEF(Invalidate_ttwidget)(ttwidget o) {
    tttheme t = myTheme(o);
    
#ifdef ttwidget_col
    /* in case o->col is client-visible */
    FIRE_EVENT(o->col = t->bg[tttheme_bg_normal], o, ttwidget_col, t->bg[tttheme_bg_normal], o->col);
#else
    o->col = t->bg[tttheme_bg_normal];
#endif
}
static ttbyte DEF(SetXY_ttwidget)(ttwidget o, ttshort x, ttshort y) {
    o->x = x;
    o->y = y;
    return TT_TRUE;
}
static ttbyte DEF(SetWH_ttwidget)(ttwidget o, ttshort w, ttshort h) {
    o->w = w;
    o->h = h;
    return TT_TRUE;
}
static ttbyte DEF(SetXl_ttwidget)(ttwidget o, ttint xl) {
    o->xl = xl;
    return TT_TRUE;
}
static ttbyte DEF(SetYl_ttwidget)(ttwidget o, ttint yl) {
    o->yl = yl;
    return TT_TRUE;
}
static ttbyte DEF(SetWl_ttwidget)(ttwidget o, ttint wl) {
    o->wl = wl;
    return TT_TRUE;
}
static ttbyte DEF(SetHl_ttwidget)(ttwidget o, ttint hl) {
    o->hl = hl;
    return TT_TRUE;
}
static ttbyte DEF(SetTooltip_ttwidget)(ttwidget o, tttooltip t) {
    if (o->tooltip != t) {
	if (o->tooltip)
	    TDEL(o->tooltip);
	if ((o->tooltip = t))
	    return t->Class->SetWidget(t, o);
    }
    return TT_TRUE;
}
static void DEF(SetLayout_ttwidget)(ttwidget o, ttlayout l) {
    TDEL(o->layout);
    o->layout = l;
    o->Class->Validate(o);
}
    
/* ttlabel */
static NEW(ttlabel) {
    if ((o = NEWSUPER(ttlabel))) {
	o->text_len = 0;
	o->text = NULL;
	o->col = myTheme(o)->bg[tttheme_bg_label];
    }
    return o;
}
static DEL(ttlabel) {
    if (o) {
	if (o->text) {
	    TTFreeMem(o->text);
	    o->text = NULL;
	}
	DELSUPER(ttlabel);
    }
}
static void DEF(SetFontD_ttlabel)(ttlabel o, ttopaque text_len, TT_ARG_DIE TT_ARG_ARRAY((_P(2))) ttfont * text) {
    if (o->text)
	TTFreeMem(o->text);
    o->text_len = text_len;
    o->text = text;
    if (o->parent && o->vflags & ttvisible_vflags_visible)
	Expose_ttvisible((ttvisible)o, ttvisible_repaint_args_WHOLE);
}

/* tttooltip */
static NEW(tttooltip) {
    if ((o = NEWSUPER(tttooltip))) {
	o->widget = (ttwidget)0;
    }
    return o;
}
static ttbyte DEF(SetWidget_tttooltip)(tttooltip o, ttwidget w) {
    if (o->widget != w) {
	o->widget = w;
	
	/*
	 * CAUTION: infinite loop risk:
	 * we call SetTooltip_ttwidget(), which may call us.
	 * 
	 * solution: we do not call it if w->tooltip is already correct
	 */
	if (w && w->tooltip != o)
	    return w->Class->SetTooltip(w, o);
    }
    return TT_TRUE;
}
static DEL(tttooltip) {
    if (o) {
	if (o->widget) {
	    /* could FIRE_EVENT() here, but this is more compact */
	    TTSetWidget_tttooltip((tt_obj)o->id, TT_NOID);
	}
	DELSUPER(tttooltip);
    }
}


/* ttbuttongroup */
static NEW(ttbuttongroup) {
    if ((o = NEWSUPER(ttbuttongroup))) {
	o->group_first = o->group_last = o->checked = (ttradiobutton)0;
    }
    return o;
}
static DEL(ttbuttongroup) {
    while (o->group_first) {
	o->Class->Remove(o, o->group_first);
    }
    DELSUPER(ttbuttongroup);
}
static void DEF(Add_ttbuttongroup)(ttbuttongroup g, ttradiobutton o) {
    if (o && !o->group) {
	
	if (g->checked && (o->vflags & ttcheckbutton_vflags_checked))
	    /* we already have a checked button, uncheck this */
	    o->Class->SetChecked(o, TT_FALSE);
	    
	if ((o->group_prev = g->group_last))
	    g->group_last->group_next = o;
	else
	    g->group_first = o;
	g->group_last = o;
	o->group_next = NULL;
	o->group = g;
	
	    
	if (!g->checked && (o->vflags & ttcheckbutton_vflags_checked)) {
	    /* we do not have a checked button, use this as the checked one */
	    FIRE_EVENT(g->checked = o, g,
		       ttbuttongroup_checked, (ttany)(opaque)o, (ttany)0);
	}
    }
}
static void DEF(Remove_ttbuttongroup)(ttbuttongroup g, ttradiobutton o) {
    if (o && o->group == g) {
	if (o == g->checked)
	    g->Class->SetChecked(g, (ttradiobutton)0);
	
	if (o->group_prev)
	    o->group_prev->group_next = o->group_next;
	else
	    g->group_first = o->group_next;
	if (o->group_next)
	    o->group_next->group_prev = o->group_prev;
	else
	    g->group_last = o->group_prev;
	
	o->group_prev = o->group_next = (ttradiobutton)0;
	o->group = (ttbuttongroup)0;
    }
}
static void DEF(SetChecked_ttbuttongroup)(ttbuttongroup g, ttradiobutton o) {
    ttradiobutton c;
    
    if (o != (c = g->checked)) {
	/*
	 * CAUTION: infinite loop risk:
	 * we call SetChecked_ttradiobutton(), which may call us.
	 * 
	 * solution: we update g->checked *before* calling it.
	 */
	FIRE_EVENT(g->checked = o, g,
		   ttbuttongroup_checked, (ttany)(opaque)o, (ttany)(opaque)c);
	
	if (c)
	    c->Class->SetChecked(c, TT_FALSE);
	if (o)
	    o->Class->SetChecked(o, TT_TRUE);
    }
}


/* ttanybutton */
static NEW(ttanybutton) {
    if ((o = NEWSUPER(ttanybutton))) {
	o->text = NULL;
	o->text_width = o->text_height = 0;
	o->col = myTheme(o)->bg[tttheme_bg_anybutton];
	
	TTWriteMem(o->theme_shape, '\0', sizeof(o->theme_shape));
	return o;
    }
    return NULL;
}
static void DEF(Invalidate_ttanybutton)(ttanybutton o) {
    ttbyte i;
    
#ifdef DEBUG_TT_HW_NULL
    fprintf(stderr, "null_Invalidate_ttanybutton(o=0x%X)\n",
	    (unsigned)o->id);
#endif
    for (i = 0; i < sizeof(o->theme_shape)/sizeof(o->theme_shape[0]); i++) {
	if (o->theme_shape[i].attr) {
	    TTFreeMem(o->theme_shape[i].attr);
	    o->theme_shape[i].attr = NULL;
	}
    }
}
static DEL(ttanybutton) {
    if (o) {
	o->Class->Invalidate(o);
    
	if (o->text) {
	    TTFreeMem(o->text);
	    o->text = NULL;
	}
	DELSUPER(ttanybutton);
    }
}
static void DEF(SetAttrD_ttanybutton)(ttanybutton o, ttshort width, ttshort height, ttshort pitch, TT_ARG_DIE TT_ARG_ARRAY((_P(3),*,_P(4))) ttattr * text) {
    o->Class->Invalidate(o);
    
    o->text_width = width;
    o->text_height = height;
    if (o->text)
	TTFreeMem(o->text);
    o->text = text;
    
    
    if (o->parent && o->vflags & ttvisible_vflags_visible)
	Expose_ttvisible((ttvisible)o, ttvisible_repaint_args_WHOLE);
}
static ttuint DEF(CacheThemeShape_ttanybutton)(ttanybutton o) {
    return (ttuint)-1;
}
static void null_AddShape(ttattr *attr, ttshort pitch, ttshort w, ttshort h,
			  ttattr *tattr, ttshort tw, ttshort th,
			  ttshort b_left, ttshort b_up, ttshort b_right, ttshort b_down,
			  ttbyte also_inside) {
    ttshort ti, i, tj, j;
    ttattr a;
    
    /* set inside */
    if (also_inside) {
	for (j = tj = b_up; j < h - b_down; j++, tj++) {
	    if (tj >= th - b_down)
		tj = b_up;
	    for (i = ti = b_left; i < w - b_right; i++, ti++) {
		if (ti >= tw - b_right)
		    ti = b_left;
		if ((a = tattr[ti+tj*tw]))
		    attr[i+j*pitch] = a;
	    }
	}
    }
    /* set left and right upper corners */
    for (j = 0; j < b_up; j++) {
	for (i = 0; i < b_left; i++) {
	    if ((a = tattr[i+j*tw]))
		attr[i+j*pitch] = a;
	}
	
	for (i = 0; i < b_right; i++) {
	    if ((a = tattr[i+(j+1)*tw-b_right]))
		attr[i+(j+1)*pitch-b_right] = a;
	}
    }
    /* set left and right borders */
    for (j = tj = b_up; j < h - b_down; j++, tj++) {
	if (tj >= tw - b_down)
	    tj = b_up;
	for (i = 0; i < b_left; i++) {
	    if ((a = tattr[i+tj*tw]))
		attr[i+j*pitch] = a;
	}
	for (i = 0; i < b_right; i++) {
	    if ((a = tattr[i+(tj+1)*tw-b_right]))
		attr[i+(j+1)*pitch-b_right] = a;
	}
    }
    /* set left and right lower corners */
    for (j = 0; j < b_down; j++) {
	for (i = 0; i < b_left; i++) {
	    if ((a = tattr[i+(th-b_down+j)*tw]))
		attr[i+(h-b_down+j)*pitch] = a;
	}
	for (i = 0; i < b_right; i++) {
	    if ((a = tattr[i+(th-b_down+j+1)*tw-b_right]))
		attr[i+(h-b_down+j+1)*pitch-b_right] = a;
	}
    }
    /* set upper border */
    for (j = 0; j < b_up; j++) {
	for (i = ti = b_left; i < w - b_right; i++, ti++) {
	    if (ti >= tw - b_right)
		ti = 0;
	    if ((a = tattr[ti+j*tw]))
		attr[i+j*pitch] = a;
	}
    }
    /* set lower border */
    for (j = 0; j < b_down; j++) {
	for (i = ti = b_left; i < w - b_right; i++, ti++) {
	    if (ti >= tw - b_right)
		ti = b_left;
	    if ((a = tattr[ti+(th-b_down+j)*tw]))
		attr[i+(h-b_down+j)*pitch] = a;
	}
    }
}
#define null_AddShapeI(attr, pitch, w, h, tattr, tw, th, b_left, b_up, b_right, b_down) \
    null_AddShape((attr), (pitch), (w), (h), (tattr), (tw), (th), (b_left), (b_up), (b_right), (b_down), TT_TRUE)

#define null_AddShapeN(attr, pitch, w, h, tattr, tw, th, b_left, b_up, b_right, b_down) \
    null_AddShape((attr), (pitch), (w), (h), (tattr), (tw), (th), (b_left), (b_up), (b_right), (b_down), TT_FALSE)

static void null_AddText(ttattr *attr, ttshort w, ttattr *text, ttshort tw, ttshort th) {
    ttshort _tw;
    ttfont f;
    ttcol c;
    
    for (; th; th--) {
	for (_tw = tw; _tw; _tw--, attr++, text++) {
	    f = TTGetFont_ttattr(*text);
	    c = TTGetCol_ttattr(*attr);
	    *attr = TTAttr(c, f);
	}
	attr += w - tw;
    }
}
static ttuint null_CacheThemeShape_anybutton(ttanybutton o, ttuint statemask, ttuint case_n, TT_CONST ttuint case_flags[][3]) {
    ttuint i, vflags;
    
    vflags = o->vflags & statemask;
    
    for (i = 0; i < case_n; i++) {
	if (case_flags[i][0] == vflags)
	    break;
    }
    if (i >= case_n)
	return (ttuint)-1;
    
    if (o->theme_shape[case_flags[i][1]].attr)
	return case_flags[i][1];

    {
	ttbyte button_i = i;
	ttbyte theme_i = case_flags[i][2];
	tttheme t = myTheme(o);
	ttshape t_shape = &t->shape[theme_i][0], s_shape =  &t->shape[theme_i][1];
	ttattr *shape;
	ttshort w = o->text_width, h = o->text_height;
	ttshort b_left = t_shape->border[tt_x_left], b_up = t_shape->border[tt_y_up];
	ttshort b_right = t_shape->border[tt_x_right], b_down = t_shape->border[tt_y_down];
	
	if ((shape = (ttattr *)TTAllocMem((w += b_left+b_right) * (h += b_up+b_down) * sizeof(ttattr) ))) {
	    
	    TTWriteMem(shape, '\0', w * h * sizeof(ttattr));
	    
	    o->theme_shape[button_i].width = w;
	    o->theme_shape[button_i].height = h;
	    TTCopyMem(t_shape->border, o->theme_shape[button_i].border, 4*sizeof(ttshort));
	    
	    null_AddShapeI(shape, w, w, h, t_shape->attr, t_shape->width, t_shape->height,
			   b_left, b_up, b_right, b_down);
	    
	    null_AddShapeI(shape, w, w, h, s_shape->attr, s_shape->width, s_shape->height,
			   s_shape->border[tt_x_left], s_shape->border[tt_y_up],
			   s_shape->border[tt_x_right], s_shape->border[tt_y_down]);
	    
	    null_AddText(shape + b_left + w * b_up, w, o->text, o->text_width, o->text_height);

	    o->theme_shape[case_flags[i][1]].attr = shape;

	    if (o->w < w || o->h < h)
		/* could FIRE_EVENT() here, but this is more compact */
		TTSetWH_ttwidget((tt_obj)o->id, w, h);

	    return case_flags[i][1];
	}

    }
    return (ttuint)-1;    
}



/* ttbutton */
static void null_MouseListener_ttbutton(ttany arg) {
    ttbutton o;
    tteventbig ev;
    ttuint old_vflags, vflags;
    ttbyte inside, activated = TT_FALSE;
    
    /* default mouse handler */
    
    if (!((ev = ID2(tteventbig,arg)) && TTAssert(ev) &&
	  TTAssert(ev->evtype == ttevent_evtype_mouse) &&
	  (o = (ttbutton)ev->component)
	  && TTAssert(IS(ttbutton,o))))
	
	return;
    
    vflags = old_vflags = o->vflags;
    inside = ev->x >= 0 && ev->y >= 0 && ev->x < o->w && ev->y < o->h;
    
    switch (ev->evcode) {
      case ttevent_evcode_mouse_move:
	if (inside)
	    vflags |= ttanybutton_vflags_prelight;
	else
	    vflags &= ~ttanybutton_vflags_prelight;
	break;
      case ttevent_evcode_mouse_press_1:
	vflags &= ~ttanybutton_vflags_prelight;
	vflags |= ttanybutton_vflags_pressed;
	break;
      case ttevent_evcode_mouse_move | ttevent_evcode_mouse_hold_1:
	if (inside)
	    vflags |= ttanybutton_vflags_pressed;
	else
	    vflags &= ~ttanybutton_vflags_pressed;
	break;
      case ttevent_evcode_mouse_release_1:
	vflags &= ~ttanybutton_vflags_pressed;
	if (inside) {
	    vflags |= ttanybutton_vflags_prelight;
	    
	    activated = TT_TRUE;
	}
	break;
      default:
	break;
    }
    
    if (vflags != old_vflags) {
	FIRE_EVENT(o->vflags = vflags, o, ttvisible_vflags, vflags, old_vflags);
	
	if (o->parent && (o->vflags & ttvisible_vflags_visible))
	    Expose_ttvisible((ttvisible)o, ttvisible_repaint_args_WHOLE);
    }
    if (activated)
	FireSimpleEvent((ttcomponent)o, ttevent_evtype_activate);
}
static ttlistener null_CreateListener_ttbutton(ttbutton o, ttuint evcode) {
    return CreateE_ttlistener
	((ttcomponent)o, Create_ttevent(ttevent_evtype_mouse, evcode, 0),
	 ttlistener_lflags_arg0_event,
	 (ttlistener_fn)null_MouseListener_ttbutton, (ttany)0);
}
static NEW(ttbutton) {
    if ((o = NEWSUPER(ttbutton))) {
	if (null_CreateListener_ttbutton(o, ttevent_evcode_mouse_move) &&
	    null_CreateListener_ttbutton(o, ttevent_evcode_mouse_press_1) &&
	    null_CreateListener_ttbutton(o, ttevent_evcode_mouse_move | ttevent_evcode_mouse_hold_1) &&
	    null_CreateListener_ttbutton(o, ttevent_evcode_mouse_release_1)) {
	    
	    return o;
	}
	TDEL(o);
    }
    return (ttbutton)0;
}
static void DEF(SetPressed_ttbutton)(ttbutton o, byte pressed) {
    if (pressed)
	o->vflags |= ttanybutton_vflags_pressed;
    else
	o->vflags &= ~ttanybutton_vflags_pressed;

    if (o->vflags != o->vflags && (o->vflags & ttvisible_vflags_visible) && o->parent)
	Expose_ttvisible((ttvisible)o, ttvisible_repaint_args_WHOLE);
}
static ttuint DEF(CacheThemeShape_ttbutton)(ttbutton o) {
    static TT_CONST ttuint case_flags[][3] = {
	{ ttanybutton_vflags_normal,   ttbutton_shape_normal,   tttheme_shape_ttbutton_normal,   },
	{ ttanybutton_vflags_prelight, ttbutton_shape_prelight, tttheme_shape_ttbutton_prelight, },
	{ ttanybutton_vflags_pressed,  ttbutton_shape_pressed,  tttheme_shape_ttbutton_pressed,  },
	{ ttanybutton_vflags_disabled, ttbutton_shape_disabled, tttheme_shape_ttbutton_disabled, },
    };
    return null_CacheThemeShape_anybutton((ttanybutton)o, ttanybutton_vflags_statemask, sizeof(case_flags)/sizeof(case_flags[0]), case_flags);
}


/* ttcheckbutton */
static void null_MouseListener_ttcheckbutton(ttany arg) {
    ttcheckbutton o;
    ttbyte checked;
    
    
    if (TTAssert(o = ID2(ttcheckbutton,arg))) {
	if (IS(ttradiobutton,o))
	    /* always set to `checked' state upon `activate' event */
	    checked = TT_TRUE;
	else /* IS(ttcheckbutton,o) */
	    /* toggle `checked' state upon `activate' event */
	    checked = !(o->vflags & ttcheckbutton_vflags_checked);
	
	/* could FIRE_EVENT() here, but this is more compact */
	TTSetChecked_ttcheckbutton((tt_obj)o->id, checked);
    }
}
static NEW(ttcheckbutton) {
    if ((o = NEWSUPER(ttcheckbutton))) {
	if (Create_ttlistener
	    ((ttcomponent)o, ttevent_evtype_activate,
	     ttlistener_lflags_arg0_component,
	     (ttlistener_fn)null_MouseListener_ttcheckbutton, (ttany)0)) {
	    
	    return o;
	}
	TDEL(o);
    }
    return NULL;
}
static void DEF(SetChecked_ttcheckbutton)(ttcheckbutton o, ttbyte checked) {
    ttuint old_vflags = o->vflags;
    
    if (checked)
	o->vflags |= ttcheckbutton_vflags_checked;
    else
	o->vflags &= ~ttcheckbutton_vflags_checked;

    if (o->vflags != old_vflags && (o->vflags & ttvisible_vflags_visible) && o->parent)
	Expose_ttvisible((ttvisible)o, ttvisible_repaint_args_WHOLE);
}
static ttuint DEF(CacheThemeShape_ttcheckbutton)(ttcheckbutton o) {
#define c ttcheckbutton_vflags_checked
    static TT_CONST ttuint case_flags[][3] = {
	{   ttanybutton_vflags_normal,   ttcheckbutton_shape_normal,           tttheme_shape_ttcheckbutton_normal,   },
	{   ttanybutton_vflags_prelight, ttcheckbutton_shape_prelight,         tttheme_shape_ttcheckbutton_prelight, },
	{   ttanybutton_vflags_pressed,  ttcheckbutton_shape_pressed,          tttheme_shape_ttcheckbutton_pressed,  },
	{   ttanybutton_vflags_disabled, ttcheckbutton_shape_disabled,         tttheme_shape_ttcheckbutton_disabled, },
	{ c|ttanybutton_vflags_normal,   ttcheckbutton_shape_checked_normal,   tttheme_shape_ttcheckbutton_checked_normal,   },
	{ c|ttanybutton_vflags_prelight, ttcheckbutton_shape_checked_prelight, tttheme_shape_ttcheckbutton_checked_prelight, },
	{ c|ttanybutton_vflags_pressed,  ttcheckbutton_shape_checked_pressed,  tttheme_shape_ttcheckbutton_checked_pressed,  },
	{ c|ttanybutton_vflags_disabled, ttcheckbutton_shape_checked_disabled, tttheme_shape_ttcheckbutton_checked_disabled, },
    };
#undef c
    return null_CacheThemeShape_anybutton((ttanybutton)o, ttcheckbutton_vflags_statemask, sizeof(case_flags)/sizeof(case_flags[0]), case_flags);
}


/* ttradiobutton */
static NEW(ttradiobutton) {
    if ((o = NEWSUPER(ttradiobutton))) {
	o->group = (ttbuttongroup)0;
	o->group_prev = o->group_next = (ttradiobutton)0;
    }
    return o;
}
static DEL(ttradiobutton) {
    ttbuttongroup g;
    if (o) {
	if ((g = o->group))
	    g->Class->Remove(g, o);
    
	DELSUPER(ttradiobutton);
    }
}
static void DEF(SetChecked_ttradiobutton)(ttradiobutton o, ttbyte checked) {
    ttbuttongroup g;
    
    FNSUPER(ttradiobutton)->SetChecked((SUPER(ttradiobutton))o, checked);
    
    /*
     * CAUTION: infinite loop risk:
     * we call SetChecked_ttbuttongroup(), which may call us.
     * 
     * solution: we do not call it if o->group->checked is already correct
     */
    if ((g = o->group)) {
	if (checked && g->checked != o)
	    g->Class->SetChecked(g, o);
	else if (!checked && g->checked == o)
	    g->Class->SetChecked(g, (ttradiobutton)0);
    }
}
static ttuint DEF(CacheThemeShape_ttradiobutton)(ttradiobutton o) {
#define c ttcheckbutton_vflags_checked
    static TT_CONST ttuint case_flags[][3] = {
	{   ttanybutton_vflags_normal,   ttradiobutton_shape_normal,           tttheme_shape_ttradiobutton_normal,   },
	{   ttanybutton_vflags_prelight, ttradiobutton_shape_prelight,         tttheme_shape_ttradiobutton_prelight, },
	{   ttanybutton_vflags_pressed,  ttradiobutton_shape_pressed,          tttheme_shape_ttradiobutton_pressed,  },
	{   ttanybutton_vflags_disabled, ttradiobutton_shape_disabled,         tttheme_shape_ttradiobutton_disabled, },
	{ c|ttanybutton_vflags_normal,   ttradiobutton_shape_checked_normal,   tttheme_shape_ttradiobutton_checked_normal,   },
	{ c|ttanybutton_vflags_prelight, ttradiobutton_shape_checked_prelight, tttheme_shape_ttradiobutton_checked_prelight, },
	{ c|ttanybutton_vflags_pressed,  ttradiobutton_shape_checked_pressed,  tttheme_shape_ttradiobutton_checked_pressed,  },
	{ c|ttanybutton_vflags_disabled, ttradiobutton_shape_checked_disabled, tttheme_shape_ttradiobutton_checked_disabled, },
    };
#undef c
    return null_CacheThemeShape_anybutton((ttanybutton)o, ttcheckbutton_vflags_statemask, sizeof(case_flags)/sizeof(case_flags[0]), case_flags);
}


/* ttanyscroll */
static NEW(ttanyscroll) {
    if ((o = NEWSUPER(ttanyscroll))) {
	o->orientation = ttanyscroll_orientation_x;
	o->size = o->real_size = o->view_size = o->real_view_size = 1;
	o->position = o->real_position = 0;
	o->state = ttanyscroll_state_normal;
    }
    return o;
}

static void DEF(Recalculate_ttanyscroll)(ttanyscroll o, ttint size, ttint real_size, ttint view_size, ttint position) {
    if (size != TT_MIN_ttint)
	o->size = minBOUND(size, 0);
    if (real_size != TT_MIN_ttint)
	o->real_size = minBOUND(real_size, 0);
    if (view_size != TT_MIN_ttint)
	o->view_size = BOUND(view_size, 0, o->size);
    if (position != TT_MIN_ttint) {
	if (o->size - o->view_size > 0)
	    o->position = BOUND(position, 0, o->size - o->view_size);
	else
	    o->position = 0;
    }

    if (o->size > 0) {
	/* round up */
	o->real_view_size = (o->view_size * o->real_size + o->size - 1) / o->size;
	/* round to nearest */
	o->real_position = (o->position * o->real_size + o->size / 2) / o->size;
	o->real_position = BOUND(o->real_position, 0, o->real_size - o->real_view_size);
    } else
	o->real_view_size = o->real_position = 0;
}

static ttbyte DEF(SetOrientation_ttanyscroll)(ttanyscroll o, ttbyte orientation) {
    REVALIDATE(o->orientation = orientation);
    return TT_TRUE;
}
static ttbyte DEF(SetSize_ttanyscroll)(ttanyscroll o, ttint size) {
    REVALIDATE(o->Class->Recalculate(o, size, TT_MIN_ttint, TT_MIN_ttint, TT_MIN_ttint));
    return TT_TRUE;
}
static ttbyte DEF(SetRealSize_ttanyscroll)(ttanyscroll o, ttint real_size) {
    REVALIDATE(o->Class->Recalculate(o, TT_MIN_ttint, real_size, TT_MIN_ttint, TT_MIN_ttint));
    return TT_TRUE;
}
static ttbyte DEF(SetViewSize_ttanyscroll)(ttanyscroll o, ttint view_size) {
    REVALIDATE(o->Class->Recalculate(o, TT_MIN_ttint, TT_MIN_ttint, view_size, TT_MIN_ttint));
    return TT_TRUE;
}
static ttbyte DEF(SetPosition_ttanyscroll)(ttanyscroll o, ttint position) {
    REVALIDATE(o->Class->Recalculate(o, TT_MIN_ttint, TT_MIN_ttint, TT_MIN_ttint, position));
    return TT_TRUE;
}
static ttbyte DEF(SetState_ttanyscroll)(ttanyscroll o, ttuint state) {
    REVALIDATE(o->state = state);
    return TT_TRUE;
}

static ttuint null_CacheThemeShape_anyscroll(ttanyscroll o, ttuint statemask,
					     ttuint case_n, TT_CONST ttuint case_flags[][3]) {
    ttuint i, vflags;
    
    vflags = o->vflags & statemask;
    
    for (i = 0; i < case_n; i++) {
	if (case_flags[i][0] == vflags)
	    break;
    }
    if (i >= case_n)
	return (ttuint)-1;
    
    if (o->theme_shape[case_flags[i][1]].attr)
	return case_flags[i][1];

    o->Class->Recalculate(o, TT_MIN_ttint, TT_MIN_ttint, TT_MIN_ttint, TT_MIN_ttint);

    {
	ttbyte x_or_y = o->orientation == ttanyscroll_orientation_x;
	ttbyte scroll_i = i;
	ttbyte theme_i = case_flags[i][2];
	tttheme t = myTheme(o);
	ttshape t_shape = &t->shape[theme_i][0], s_shape =  &t->shape[theme_i][1];
	ttattr *shape;
	ttshort w, h;
	ttshort b_left = t_shape->border[tt_x_left], b_up = t_shape->border[tt_y_up];
	ttshort b_right = t_shape->border[tt_x_right], b_down = t_shape->border[tt_y_down];
	ttint real_pos = o->real_position;
	
	if (x_or_y)
	    w = o->real_size, h = t_shape->height;
	else
	    w = t_shape->width, h = o->real_size;
	    
	if ((shape = (ttattr *)TTAllocMem((w += b_left+b_right) * (h += b_up+b_down) * sizeof(ttattr) ))) {
	    
	    TTWriteMem(shape, '\0', w * h * sizeof(ttattr));
	    
	    o->theme_shape[scroll_i].width = w;
	    o->theme_shape[scroll_i].height = h;
	    TTCopyMem(t_shape->border, o->theme_shape[scroll_i].border, 4*sizeof(ttshort));
	    
	    null_AddShapeI(shape, w, w, h, t_shape->attr, t_shape->width, t_shape->height,
			   b_left, b_up, b_right, b_down);
	    
	    if (x_or_y) {
		null_AddShapeI(shape + real_pos, w, o->real_view_size, h,
			       s_shape->attr, s_shape->width, s_shape->height,
			       s_shape->border[tt_x_left], s_shape->border[tt_y_up],
			       s_shape->border[tt_x_right], s_shape->border[tt_y_down]);
	    } else {
		null_AddShapeI(shape + w * real_pos, w, w, o->real_view_size,
			       s_shape->attr, s_shape->width, s_shape->height,
			       s_shape->border[tt_x_left], s_shape->border[tt_y_up],
			       s_shape->border[tt_x_right], s_shape->border[tt_y_down]);
	    }

	    o->theme_shape[case_flags[i][1]].attr = shape;
	    
	    if (o->w != w || o->h != h)
		/* could FIRE_EVENT() here, but this is more compact */
		TTSetWH_ttwidget((tt_obj)o->id, w, h);

	    return case_flags[i][1];
	}

    }
    return (ttuint)-1;    
}

/* ttscrollbar */
static DEL(ttscrollbar) {
    ttscrollpane w;
    
    if (o) {
	if ((w = o->scrollpane)) {
	    o->scrollpane = NULL;
	    
	    if (o->orientation == ttanyscroll_orientation_x)
		TTSetBarX_ttscrollpane((tt_obj)w->id, NULL);
	    else
		TTSetBarY_ttscrollpane((tt_obj)w->id, NULL);
	}
	DELSUPER(ttscrollbar);
    }
}
static void null_SyncScrollpane_ttscrollbar(ttscrollbar o, ttscrollpane w) {
    if (o->orientation == ttanyscroll_orientation_x) {
	if (o->position != w->xl)
	    /* could FIRE_EVENT() here, but this is more compact */
	    TTSetXl_ttwidget((tt_obj)w->id, o->position);
    } else if (o->orientation == ttanyscroll_orientation_y) {
	if (o->position != w->yl)
	    /* could FIRE_EVENT() here, but this is more compact */
	    TTSetYl_ttwidget((tt_obj)w->id, o->position);
    }
}
static void DEF(Recalculate_ttscrollbar)(ttscrollbar o, ttint size, ttint real_size, ttint view_size, ttint position) {
    FNSUPER(ttscrollbar)->Recalculate((SUPER(ttscrollbar))o, size, real_size, view_size, position);
    if (o->scrollpane)
	null_SyncScrollpane_ttscrollbar(o, o->scrollpane);
}
static ttuint null_ComputeMouseState_ttscrollbar(ttscrollbar o, ttint x, ttint y, ttint *ret_p) {
    /* calculate where the mouse is */
    tttheme t = myTheme(o);
    ttint rev1, rev2, fwd1, fwd2, s, S, p, P;
    ttuint i, state = ttscrollbar_state_normal;
    ttbyte inside = TT_FALSE, orientation = o->orientation == ttanyscroll_orientation_x;
    ttint b1, b2;

    i = o->Class->CacheThemeShape(o);
    if (i == (ttuint)-1)
	return state;

    if (orientation) {
	p = x;    P = y;
	s = o->w; S = o->h;
	rev1 = t->val[tttheme_val_ttscrollbar_x_arrow_rev_min];
	rev2 = t->val[tttheme_val_ttscrollbar_x_arrow_rev_max];
	fwd1 = t->val[tttheme_val_ttscrollbar_x_arrow_fwd_min];
	fwd2 = t->val[tttheme_val_ttscrollbar_x_arrow_fwd_max];
	b1 = o->theme_shape[i].border[tt_x_left];
	b2 = o->theme_shape[i].border[tt_x_right];
    } else {
	p = y;    P = x;
	s = o->h; S = o->w;
	rev1 = t->val[tttheme_val_ttscrollbar_y_arrow_rev_min];
	rev2 = t->val[tttheme_val_ttscrollbar_y_arrow_rev_max];
	fwd1 = t->val[tttheme_val_ttscrollbar_y_arrow_fwd_min];
	fwd2 = t->val[tttheme_val_ttscrollbar_y_arrow_fwd_max];
	b1 = o->theme_shape[i].border[tt_y_up];
	b2 = o->theme_shape[i].border[tt_y_down];
    }
    
    if (rev1 < 0) rev1 += s;
    if (rev2 < 0) rev2 += s;
    if (fwd1 < 0) fwd1 += s;
    if (fwd2 < 0) fwd2 += s;
    
    if (P >= 0 && P < S)
	inside = TT_TRUE;
    
    if (inside) {
	if (p >= rev1 && p <= rev2)
	    state = ttscrollbar_state_arrow_rev;
	else if (inside && p >= fwd1 && p <= fwd2)
	    state = ttscrollbar_state_arrow_fwd;
	else if (p >= b1 && p <= s - b2) {
	    p -= b1;
	    if (p < o->real_position)
		state = ttscrollbar_state_page_rev;
	    else if (p >= o->real_position + o->real_view_size)
		state = ttscrollbar_state_page_fwd;
	    else
		state = ttscrollbar_state_tab;
	    p += b1;
	}
    } else
	state = ttscrollbar_state_tab;
	
    *ret_p = p - b1;
    
    return state;
}
static void null_MouseListener_ttscrollbar(ttany arg) {
    ttscrollbar o;
    tteventbig ev;
    tttimer t;
    ttint p, delta = 0, real_delta = 0, position, real_position;
    ttuint old_vflags, vflags, state;
    ttbyte inside, set_state = TT_FALSE, set_timer = TT_FALSE;
    
    /* default mouse handler */
    
    if (!(TTAssert(ev = ID2(tteventbig,arg)) &&
	  TTAssert(ev->evtype == ttevent_evtype_mouse) &&
	  (o = (ttscrollbar)ev->component) &&
	  TTAssert(IS(ttscrollbar,o))))
	
	return;
    
    vflags = old_vflags = o->vflags;
    inside = ev->x >= 0 && ev->y >= 0 && ev->x < o->w && ev->y < o->h;

    state = null_ComputeMouseState_ttscrollbar(o, ev->x, ev->y, &p);
    
    switch (ev->evcode) {
      case ttevent_evcode_mouse_move:
	if (inside)
	    vflags |= ttanybutton_vflags_prelight;
	else
	    vflags &= ~ttanybutton_vflags_prelight;
	break;
      case ttevent_evcode_mouse_press_1:
	vflags &= ~ttanybutton_vflags_prelight;
	vflags |= ttanybutton_vflags_pressed;
	set_state = set_timer = TT_TRUE;
	
	switch (state) {
	  case ttscrollbar_state_page_rev:
	    delta = o->view_size > 1 ? 1 - o->view_size : -1;
	    break;
	  case ttscrollbar_state_page_fwd:
	    delta = o->view_size > 1 ? o->view_size - 1 : 1;
	    break;
	  case ttscrollbar_state_arrow_rev:
	    delta = -1;
	    break;
	  case ttscrollbar_state_arrow_fwd:
	    delta = 1;
	    break;
	  case ttscrollbar_state_tab:
	    state += p - o->real_position;
	    set_timer = TT_FALSE;
	    break;
	  default:
	    set_timer = TT_FALSE;
	    break;
	}
	break;
      case ttevent_evcode_mouse_move | ttevent_evcode_mouse_hold_1:
	if (o->state >= ttscrollbar_state_tab)
	    real_delta = p - o->real_position - (o->state - ttscrollbar_state_tab);
	break;
      case ttevent_evcode_mouse_release_1:
	vflags &= ~ttanybutton_vflags_pressed;
	if (inside)
	    vflags |= ttanybutton_vflags_prelight;
	state = ttscrollbar_state_normal;
	set_state = TT_TRUE;
	break;
      default:
	break;
    }
    
    if (real_delta) {
	/* be careful with rounding! round position, not delta */
	real_position = o->real_position + real_delta;
	/* round to nearest */
	position = (real_position * o->size + o->real_size/2) / o->real_size;
	delta = position - o->position;
    }
    if (delta) {
	/* could FIRE_EVENT() here, but this is more compact */
	TTSetPosition_ttanyscroll((tt_obj)o->id, o->position + delta);
    }
    if (set_state) {
	o->Class->SetState(o, state);
    }
    if ((t = o->timers) && !!(t->lflags & tttimer_lflags_enabled) != set_timer) {
	if (t->args && t->args->array_n > 1)
	    t->args->array[1] = (ttany)delta;
	
	if (set_timer)
	    TTSetDelay_tttimer((tt_obj)t->id, 0, 200 MilliSECs);
	
	t->Class->SetEnabled(t, set_timer);
    }
    if (vflags != old_vflags) {
	FIRE_EVENT(o->vflags = vflags, o, ttvisible_vflags, vflags, old_vflags);
	
	if (o->parent && (o->vflags & ttvisible_vflags_visible))
	    Expose_ttvisible((ttvisible)o, ttvisible_repaint_args_WHOLE);
    }
}
static ttlistener null_CreateMouseListener_ttscrollbar(ttscrollbar o, ttuint evcode) {
    return CreateE_ttlistener
	((ttcomponent)o, Create_ttevent(ttevent_evtype_mouse, evcode, 0),
	 ttlistener_lflags_arg0_event,
	 (ttlistener_fn)null_MouseListener_ttscrollbar, (ttany)0);
}
static void null_TimerListener_ttscrollbar(ttany arg0, ttany arg1) { 
    tttimer t;
    ttscrollbar o;
    ttint delta;
    
    /* default timer handler */
    if (!((t = ID2(tttimer,arg0)) && TTAssert(t) &&
	  (o = (ttscrollbar)t->component) &&
	  TTAssert(IS(ttscrollbar,o))))
	return;

    if ((delta = (ttint)arg1)) {
	/* could FIRE_EVENT() here, but this is more compact */
	TTSetPosition_ttanyscroll((tt_obj)o->id, o->position + delta);
    }
    /* reactivate the timer itself */
    TTSetDelay_tttimer((tt_obj)t->id, 0, 30 MilliSECs);
}
TT_INLINE tttimer null_CreateTimer_ttscrollbar(ttscrollbar o) {
    return CreateR_tttimer((ttcomponent)o, 0, (ttlistener_fn)null_TimerListener_ttscrollbar,
			   -1, 0, CreateL_ttvector(2, (ttany)0, (ttany)0), 0, 0);
}
static NEW(ttscrollbar) {
    if ((o = NEWSUPER(ttscrollbar))) {
	o->view_size = o->real_view_size = 1;
	o->scrollpane = (ttscrollpane)0;
	
	if (null_CreateMouseListener_ttscrollbar(o, ttevent_evcode_mouse_move) &&
	    null_CreateMouseListener_ttscrollbar(o, ttevent_evcode_mouse_press_1) &&
	    null_CreateMouseListener_ttscrollbar(o, ttevent_evcode_mouse_move | ttevent_evcode_mouse_hold_1) &&
	    null_CreateMouseListener_ttscrollbar(o, ttevent_evcode_mouse_release_1) &&
	    null_CreateTimer_ttscrollbar(o)) {

	    return o;
	}
	TDEL(o);
    }
    return (ttscrollbar)0;
}
static ttuint DEF(CacheThemeShape_ttscrollbar)(ttscrollbar o) {
    static TT_CONST ttuint case_flags_x[][3] = {
	{ ttanybutton_vflags_normal,   ttscrollbar_shape_normal,   tttheme_shape_ttscrollbar_x_normal,   },
	{ ttanybutton_vflags_prelight, ttscrollbar_shape_prelight, tttheme_shape_ttscrollbar_x_prelight, },
	{ ttanybutton_vflags_pressed,  ttscrollbar_shape_pressed,  tttheme_shape_ttscrollbar_x_pressed,  },
	{ ttanybutton_vflags_disabled, ttscrollbar_shape_disabled, tttheme_shape_ttscrollbar_x_disabled, },
    };
    static TT_CONST ttuint case_flags_y[][3] = {
	{ ttanybutton_vflags_normal,   ttscrollbar_shape_normal,   tttheme_shape_ttscrollbar_y_normal,   },
	{ ttanybutton_vflags_prelight, ttscrollbar_shape_prelight, tttheme_shape_ttscrollbar_y_prelight, },
	{ ttanybutton_vflags_pressed,  ttscrollbar_shape_pressed,  tttheme_shape_ttscrollbar_y_pressed,  },
	{ ttanybutton_vflags_disabled, ttscrollbar_shape_disabled, tttheme_shape_ttscrollbar_y_disabled, },
    };
    return null_CacheThemeShape_anyscroll
	((ttanyscroll)o, ttanybutton_vflags_statemask,
	 sizeof(case_flags_x)/sizeof(case_flags_x[0]),
	 o->orientation == ttanyscroll_orientation_x ? case_flags_x : case_flags_y);
}

/* ttslider */
static ttuint null_ComputeMouseState_ttslider(ttslider o, ttint x, ttint y, ttint *ret_p) {
    /* calculate where the mouse is */
    ttint s, S, p, P;
    ttuint i, state = ttslider_state_normal;
    ttbyte inside = TT_FALSE, orientation = o->orientation == ttanyscroll_orientation_x;

    i = o->Class->CacheThemeShape(o);
    if (i == (ttuint)-1)
	return state;

    if (orientation) {
	p = x;    P = y;
	s = o->w; S = o->h;
    } else {
	p = y;    P = x;
	s = o->h; S = o->w;
    }
    
    if (P >= 0 && P < S)
	inside = TT_TRUE;
    
    if (inside) {
	if (p < o->real_position)
	    state = ttslider_state_page_rev;
	else if (p >= o->real_position + o->real_view_size)
	    state = ttslider_state_page_fwd;
	else
	    state = ttslider_state_tab;
    } else
	state = ttslider_state_tab;
	
    *ret_p = p;
    
    return state;
}
static void null_MouseListener_ttslider(ttany arg) {
    ttslider o;
    tteventbig ev;
    tttimer t;
    ttint p, delta = 0, real_delta = 0, position, real_position;
    ttuint old_vflags, vflags, state;
    ttbyte inside, set_state = TT_FALSE, set_timer = TT_FALSE;
    
    /* default mouse handler */
    
    if (!(TTAssert(ev = ID2(tteventbig,arg)) &&
	  TTAssert(ev->evtype == ttevent_evtype_mouse) &&
	  (o = (ttslider)ev->component) &&
	  TTAssert(IS(ttslider,o))))
	
	return;
    
    vflags = old_vflags = o->vflags;
    inside = ev->x >= 0 && ev->y >= 0 && ev->x < o->w && ev->y < o->h;

    state = null_ComputeMouseState_ttslider(o, ev->x, ev->y, &p);
    
    switch (ev->evcode) {
      case ttevent_evcode_mouse_move:
	if (inside)
	    vflags |= ttanybutton_vflags_prelight;
	else
	    vflags &= ~ttanybutton_vflags_prelight;
	break;
      case ttevent_evcode_mouse_press_1:
	vflags &= ~ttanybutton_vflags_prelight;
	vflags |= ttanybutton_vflags_pressed;
	set_state = set_timer = TT_TRUE;
	
	switch (state) {
	  case ttslider_state_page_rev:
	    delta = -1;
	    break;
	  case ttslider_state_page_fwd:
	    delta = 1;
	    break;
	  case ttslider_state_tab:
	    state += p - o->real_position;
	    set_timer = TT_FALSE;
	    break;
	  default:
	    set_timer = TT_FALSE;
	    break;
	}
	break;
      case ttevent_evcode_mouse_move | ttevent_evcode_mouse_hold_1:
	if (o->state >= ttslider_state_tab)
	    real_delta = p - o->real_position - (o->state - ttslider_state_tab);
	break;
      case ttevent_evcode_mouse_release_1:
	vflags &= ~ttanybutton_vflags_pressed;
	if (inside)
	    vflags |= ttanybutton_vflags_prelight;
	state = ttslider_state_normal;
	set_state = TT_TRUE;
	break;
      default:
	break;
    }
    
    if (real_delta) {
	/* be careful with rounding! round position, not delta */
	real_position = o->real_position + real_delta;
	/* round to nearest */
	position = (real_position * o->size + o->real_size/2) / o->real_size;
	delta = position - o->position;
    }
    if (delta) {
	/* could FIRE_EVENT() here, but this is more compact */
	TTSetPosition_ttanyscroll((tt_obj)o->id, o->position + delta);
    }
    if (set_state) {
	o->Class->SetState(o, state);
    }
    if ((t = o->timers) && !!(t->lflags & tttimer_lflags_enabled) != set_timer) {
	if (t->args && t->args->array_n > 1)
	    t->args->array[1] = (ttany)delta;
	
	if (set_timer)
	    TTSetDelay_tttimer((tt_obj)t->id, 0, 200 MilliSECs);
	
	t->Class->SetEnabled(t, set_timer);
    }
    if (vflags != old_vflags) {
	FIRE_EVENT(o->vflags = vflags, o, ttvisible_vflags, vflags, old_vflags);
	
	if (o->parent && (o->vflags & ttvisible_vflags_visible))
	    Expose_ttvisible((ttvisible)o, ttvisible_repaint_args_WHOLE);
    }
}
static ttlistener null_CreateMouseListener_ttslider(ttslider o, ttuint evcode) {
    return CreateE_ttlistener
	((ttcomponent)o, Create_ttevent(ttevent_evtype_mouse, evcode, 0),
	 ttlistener_lflags_arg0_event,
	 (ttlistener_fn)null_MouseListener_ttslider, (ttany)0);
}
static void null_TimerListener_ttslider(ttany arg0, ttany arg1) { 
    tttimer t;
    ttslider o;
    ttint delta;
    
    /* default timer handler */
    if (!((t = ID2(tttimer,arg0)) && TTAssert(t) &&
	  (o = (ttslider)t->component) &&
	  TTAssert(IS(ttslider,o))))
	return;

    if ((delta = (ttint)arg1)) {
	/* could FIRE_EVENT() here, but this is more compact */
	TTSetPosition_ttanyscroll((tt_obj)o->id, o->position + delta);
    }
    /* reactivate the timer itself */
    TTSetDelay_tttimer((tt_obj)t->id, 0, 30 MilliSECs);
}
TT_INLINE tttimer null_CreateTimer_ttslider(ttslider o) {
    return CreateR_tttimer((ttcomponent)o, 0, (ttlistener_fn)null_TimerListener_ttslider,
			   -1, 0, CreateL_ttvector(2, (ttany)0, (ttany)0), 0, 0);
}
static NEW(ttslider) {
    if ((o = NEWSUPER(ttslider))) {
	o->slide_min = o->slide_value = 0;
	o->slide_max = 10;

	if (null_CreateMouseListener_ttslider(o, ttevent_evcode_mouse_move) &&
	    null_CreateMouseListener_ttslider(o, ttevent_evcode_mouse_press_1) &&
	    null_CreateMouseListener_ttslider(o, ttevent_evcode_mouse_move | ttevent_evcode_mouse_hold_1) &&
	    null_CreateMouseListener_ttslider(o, ttevent_evcode_mouse_release_1) &&
	    null_CreateTimer_ttslider(o)) {
	    
	    return o;
	}
	TDEL(o);
    }
    return NULL;
}
static void DEF(Recalculate_ttslider)(ttslider o, ttint size, ttint real_size, ttint view_size, ttint position) {
    tttheme t = myTheme(o);
    ttint real_size_, real_view_size, view_size_;

    if (size < 0) {
	if (o->real_size - o->real_view_size > 0) {
	    size = o->slide_max - o->slide_min;
	    if (size < 0)
		size = -size;
	    size = o->real_size * size / (o->real_size - o->real_view_size);
	} else
	    size = 0;
    }

    real_view_size = size > 0
	? o->orientation == ttanyscroll_orientation_x
	? t->shape[ttslider_shape_normal][1].width
	: t->shape[ttslider_shape_normal][1].height
	: 0;

    real_size_ = real_size >= 0 ? real_size : o->real_size;
    view_size_ = real_size_ > 0 ? real_view_size * size / real_size_ : 0;

    if (size == o->size)
	size = TT_MIN_ttint;

    FNSUPER(ttslider)->Recalculate((SUPER(ttslider))o, size, real_size, view_size_, position);
}
static ttuint DEF(CacheThemeShape_ttslider)(ttslider o) {
    static TT_CONST ttuint case_flags_x[][3] = {
	{ ttanybutton_vflags_normal,   ttslider_shape_normal,   tttheme_shape_ttslider_x_normal,   },
	{ ttanybutton_vflags_prelight, ttslider_shape_prelight, tttheme_shape_ttslider_x_prelight, },
	{ ttanybutton_vflags_pressed,  ttslider_shape_pressed,  tttheme_shape_ttslider_x_pressed,  },
	{ ttanybutton_vflags_disabled, ttslider_shape_disabled, tttheme_shape_ttslider_x_disabled, },
    };
    static TT_CONST ttuint case_flags_y[][3] = {
	{ ttanybutton_vflags_normal,   ttslider_shape_normal,   tttheme_shape_ttslider_y_normal,   },
	{ ttanybutton_vflags_prelight, ttslider_shape_prelight, tttheme_shape_ttslider_y_prelight, },
	{ ttanybutton_vflags_pressed,  ttslider_shape_pressed,  tttheme_shape_ttslider_y_pressed,  },
	{ ttanybutton_vflags_disabled, ttslider_shape_disabled, tttheme_shape_ttslider_y_disabled, },
    };
    return null_CacheThemeShape_anyscroll
	((ttanyscroll)o, ttanybutton_vflags_statemask,
	 sizeof(case_flags_x)/sizeof(case_flags_x[0]),
	 o->orientation == ttanyscroll_orientation_x ? case_flags_x : case_flags_y);
}
static ttbyte DEF(SetSize_ttslider)(ttslider o, ttint size) {
    /* do-nothing; use SetSlideMin() and SetSlideMax() instead */
    return TT_FALSE;
}
static ttbyte DEF(SetSlideMin_ttslider)(ttslider o, ttint slide_min) {
    REVALIDATE(o->slide_min = slide_min);
    return TT_TRUE;
}
static ttbyte DEF(SetSlideMax_ttslider)(ttslider o, ttint slide_max) {
    REVALIDATE(o->slide_max = slide_max);
    return TT_TRUE;
}
static ttbyte DEF(SetSlideValue_ttslider)(ttslider o, ttint slide_value) {
    ttint low, high, position;
    
    REVALIDATE(
	       if (o->slide_min < o->slide_max) {
		   low = o->slide_min;
		   high = o->slide_max;
	       } else {
		   low = o->slide_max;
		   high = o->slide_min;
	       }
	       o->slide_value = slide_value = BOUND(slide_value, low, high);
	       
	       if ((position = slide_value - o->slide_min) < 0)
	       position = -position;
	       
	       if (position != o->position)
	       FIRE_EVENT(
			  o->Class->Recalculate(o, TT_MIN_ttint, TT_MIN_ttint, TT_MIN_ttint, position),
			  o, ttanyscroll_position, position, o->position
			  )
	       );
    
    return TT_TRUE;
}
static ttbyte DEF(SetPosition_ttslider)(ttslider o, ttint position) {
    ttint sign, slide_value;
    
    REVALIDATE(
	       o->position = position = BOUND(position, 0, o->size);
	       if (o->slide_min < o->slide_max)
	       sign = 1;
	       else
	       sign = -1;
	       slide_value = o->slide_min + sign * position;
	       if (slide_value != o->slide_value)
	       FIRE_EVENT(
			  o->slide_value = slide_value,
			  o, ttslider_slide_value, slide_value, o->slide_value
			  );
	       o->Class->Recalculate(o, TT_MIN_ttint, TT_MIN_ttint, TT_MIN_ttint, position)
	       );

    return TT_TRUE;
}


/* ttprogressbar */
static NEW(ttprogressbar) {
    if ((o = NEWSUPER(ttprogressbar))) {
	o->size = 100;
	o->view_size = o->real_view_size = o->real_position_frac_above = 0;
	o->real_position_frac_below = 1;
    }
    return o;
}
static void DEF(Recalculate_ttprogressbar)(ttprogressbar o, ttint size, ttint real_size, ttint view_size, ttint position) {
    
    FNSUPER(ttscrollbar)->Recalculate((SUPER(ttprogressbar))o, size, real_size, view_size, position);

    if (o->size > 0) {
	static TT_CONST ttuint case_flags_i[] = {
	    tttheme_shape_ttprogressbar_x_normal,
	    tttheme_shape_ttprogressbar_x_disabled,
	    tttheme_shape_ttprogressbar_y_normal,
	    tttheme_shape_ttprogressbar_y_disabled,
	};
	ttuint theme_i = 
	    (o->orientation == ttanyscroll_orientation_x ? 2 : 0) |
	    ((o->vflags & ttprogressbar_vflags_statemask) == ttprogressbar_vflags_disabled ? 1 : 0);
	
	ttshape shape = & myTheme(o)->shape[case_flags_i[theme_i]][0];
	
	ttuint frac_below = o->real_position_frac_below = 
	    o->orientation == ttanyscroll_orientation_x
	    ? shape->width - shape->border[tt_x_left] - shape->border[tt_x_right]
	    : shape->height- shape->border[tt_y_up] - shape->border[tt_y_down];

	/* round to nearest */
	ttint real_pos = (o->position * o->real_size * frac_below + o->size / 2) / o->size;
	
	o->real_position = real_pos / frac_below;
	o->real_position = BOUND(o->real_position, 0, o->real_size - o->real_view_size);

	o->real_position_frac_above = real_pos % frac_below;
    } else
	o->real_position_frac_above = 0;
}
static void null_AddShapeI_progressbar(ttattr *attr, ttshort pitch, ttshort w, ttshort h,
				       ttattr *tattr, ttshort tw, ttshort th,
				       ttshort b_left, ttshort b_up, ttshort b_right, ttshort b_down,
				       ttprogressbar o) {
    
    ttshort ti, i, tj, j;
    ttint above = o->real_position_frac_above;
    ttshort max = o->real_position_frac_below - 1;
    
    
    if (o->orientation == ttanyscroll_orientation_x) {
	/* set horizontal progressbar inside */
	for (j = tj = b_up; j < h - b_down; j++, tj++) {
	    if (tj >= th - b_down)
		tj = b_up;
	    for (i = b_left; i < w - b_right; i++) {
		ti = i - b_left;
		if (ti < o->real_position)
		    attr[i+j*pitch] = tattr[max + tj*tw];
		else if (ti == o->real_position)
		    attr[i+j*pitch] = tattr[above + tj*tw];
		else
		    attr[i+j*pitch] = tattr[0 + tj*tw];
	    }
	}
    } else {
	/* set vertical progressbar inside */
	for (j = b_up; j < h - b_down; j++) {
	    tj = j - b_up;
	    for (i = ti = b_left; i < w - b_right; i++, ti++) {
		if (ti >= tw - b_right)
		    ti = b_left;
		if (tj < o->real_position)
		    attr[i+j*pitch] = tattr[ti + max*tw];
		else if (tj == o->real_position)
		    attr[i+j*pitch] = tattr[ti + above*tw];
		else
		    attr[i+j*pitch] = tattr[ti + 0*tw];
	    }
	}
    }
}
static ttuint DEF(CacheThemeShape_ttprogressbar)(ttprogressbar o) {
    static TT_CONST ttuint case_flags_x[][3] = {
	{ ttprogressbar_vflags_normal,   ttprogressbar_shape_normal,   tttheme_shape_ttprogressbar_x_normal,   },
	{ ttprogressbar_vflags_disabled, ttprogressbar_shape_disabled, tttheme_shape_ttprogressbar_x_disabled, },
    };
    static TT_CONST ttuint case_flags_y[][3] = {
	{ ttprogressbar_vflags_normal,   ttprogressbar_shape_normal,   tttheme_shape_ttprogressbar_y_normal,   },
	{ ttprogressbar_vflags_disabled, ttprogressbar_shape_disabled, tttheme_shape_ttprogressbar_y_disabled, },
    };

    /* this is almost equal to null_CacheThemeShape_anyscroll(), except where noted below */
    
    ttuint statemask = ttprogressbar_vflags_statemask;
    ttuint case_n = sizeof(case_flags_x)/sizeof(case_flags_x[0]);
    TT_CONST ttuint (*case_flags)[3] = o->orientation == ttanyscroll_orientation_x ? case_flags_x : case_flags_y;
    ttuint i, vflags;
    
    vflags = o->vflags & statemask;
    
    for (i = 0; i < case_n; i++) {
	if (case_flags[i][0] == vflags)
	    break;
    }
    if (i >= case_n)
	return (ttuint)-1;
    
    if (o->theme_shape[case_flags[i][1]].attr)
	return case_flags[i][1];

    o->Class->Recalculate(o, TT_MIN_ttint, TT_MIN_ttint, TT_MIN_ttint, TT_MIN_ttint);

    {
	ttbyte x_or_y = o->orientation == ttanyscroll_orientation_x;
	ttbyte scroll_i = i;
	ttbyte theme_i = case_flags[i][2];
	tttheme t = myTheme(o);
	ttshape t_shape = &t->shape[theme_i][0], s_shape =  &t->shape[theme_i][1];
	ttattr *shape;
	ttshort w, h;
	ttshort b_left = t_shape->border[tt_x_left], b_up = t_shape->border[tt_y_up];
	ttshort b_right = t_shape->border[tt_x_right], b_down = t_shape->border[tt_y_down];
	ttint real_pos = o->real_position;
	
	if (x_or_y)
	    w = o->real_size, h = t_shape->height;
	else
	    w = t_shape->width, h = o->real_size;
	    
	if ((shape = (ttattr *)TTAllocMem((w += b_left+b_right) * (h += b_up+b_down) * sizeof(ttattr) ))) {
	    
	    TTWriteMem(shape, '\0', w * h * sizeof(ttattr));
	    
	    o->theme_shape[scroll_i].width = w;
	    o->theme_shape[scroll_i].height = h;
	    TTCopyMem(t_shape->border, o->theme_shape[scroll_i].border, 4*sizeof(ttshort));
	    
	    /* this differs from null_CacheThemeShape_anyscroll() : */
#if 1
	    null_AddShapeN(shape, w, w, h, t_shape->attr, t_shape->width, t_shape->height,
			   b_left, b_up, b_right, b_down);
	    null_AddShapeI_progressbar(shape, w, w, h, t_shape->attr, t_shape->width, t_shape->height,
				       b_left, b_up, b_right, b_down, o);
#else
	    null_AddShapeI(shape, w, w, h, t_shape->attr, t_shape->width, t_shape->height,
			   b_left, b_up, b_right, b_down);
#endif
	    
	    if (x_or_y) {
		null_AddShapeI(shape + real_pos, w, o->real_view_size, h,
			       s_shape->attr, s_shape->width, s_shape->height,
			       s_shape->border[tt_x_left], s_shape->border[tt_y_up],
			       s_shape->border[tt_x_right], s_shape->border[tt_y_down]);
	    } else {
		null_AddShapeI(shape + w * real_pos, w, w, o->real_view_size,
			       s_shape->attr, s_shape->width, s_shape->height,
			       s_shape->border[tt_x_left], s_shape->border[tt_y_up],
			       s_shape->border[tt_x_right], s_shape->border[tt_y_down]);
	    }

	    o->theme_shape[case_flags[i][1]].attr = shape;
	    
	    if (o->w != w || o->h != h)
		/* could FIRE_EVENT() here, but this is more compact */
		TTSetWH_ttwidget((tt_obj)o->id, w, h);

	    return case_flags[i][1];
	}

    }
    return (ttuint)-1;    
}

/* ttscrollpane */
static NEW(ttscrollpane) {
    ttscrollbar sx, sy;
    
    if ((o = NEWSUPER(ttscrollpane))) {
	if ((sx = Create1_ttscrollbar(ttanyscroll_orientation_x)) &&
	    (sy = Create1_ttscrollbar(ttanyscroll_orientation_y))) {
	    
	    o->Class->SetBarX(o, sx);
	    o->Class->SetBarY(o, sy);
	    
	    return o;
	}
	TDEL(o);
    }
    return (ttscrollpane)0;
}
static DEL(ttscrollpane) {
    if (o) {
	if (o->bar_x) {
	    TDEL(o->bar_x);
	    o->bar_x = NULL;
	}
	if (o->bar_y) {
	    TDEL(o->bar_y);
	    o->bar_y = NULL;
	}
	DELSUPER(ttscrollpane);
    }
}
static ttbyte DEF(SetWH_ttscrollpane)(ttscrollpane o, ttshort w, ttshort h) {
    ttshort ow = o->w, oh = o->h;
    ttscrollbar sx = o->bar_x, sy = o->bar_y;
    
    
    FNSUPER(ttscrollpane)->SetWH((SUPER(ttscrollpane))o, w, h);
    
    w = o->w; h = o->h;
    
    /* a scrollpane must have logic size at least equal to real size */
    /* and must keep in sync with its scrollbars */
    
    /*
     * must call these FIRST, or scrollbars size will be wrong and will
     * incorrectly bound also their view_size
     */
    if (o->wl < w)
	TTSetWl_ttwidget((tt_obj)o->id, w);
    if (o->hl < h)
	TTSetHl_ttwidget((tt_obj)o->id, h);
    
    if (w != ow) {
	if (sx) {
	    TTSetRealSize_ttanyscroll((tt_obj)sx->id, w - 2);
	    TTSetViewSize_ttanyscroll((tt_obj)sx->id, w);
	}
	if (sy)
	    TTSetXY_ttwidget((tt_obj)sy->id, w, 0);
    }
    if (h != oh) {
	if (sy) {
	    TTSetRealSize_ttanyscroll((tt_obj)sy->id, h - 2);
	    TTSetViewSize_ttanyscroll((tt_obj)sy->id, h);
	}
	if (sx)
	    TTSetXY_ttwidget((tt_obj)sx->id, 0, h);
    }
    return TT_TRUE;
}
static ttbyte DEF(SetXl_ttscrollpane)(ttscrollpane o, ttint xl) {
    ttint oxl = o->xl;
    
    FNSUPER(ttscrollpane)->SetXl((SUPER(ttscrollpane))o, xl);
    
    xl = o->xl;
    
    /* a scrollpane must keep in sync with its scrollbars */
    if (xl != oxl && o->bar_x)
	TTSetPosition_ttanyscroll((tt_obj)o->bar_x->id, xl);
    
    return TT_TRUE;
}
static ttbyte DEF(SetYl_ttscrollpane)(ttscrollpane o, ttint yl) {
    ttint oyl = o->yl;
    
    FNSUPER(ttscrollpane)->SetYl((SUPER(ttscrollpane))o, yl);
    
    yl = o->yl;
    
    /* a scrollpane must keep in sync with its scrollbars */
    if (yl != oyl && o->bar_y)
	TTSetPosition_ttanyscroll((tt_obj)o->bar_y->id, yl);

    return TT_TRUE;
}
static ttbyte DEF(SetWl_ttscrollpane)(ttscrollpane o, ttint wl) {
    ttint owl = o->wl;
    
    FNSUPER(ttscrollpane)->SetWl((SUPER(ttscrollpane))o, wl);
    
    wl = o->wl;
    
    /* a scrollpane must keep in sync with its scrollbars */
    if (wl != owl && o->bar_x)
	TTSetSize_ttanyscroll((tt_obj)o->bar_x->id, wl);

    return TT_TRUE;
}
static ttbyte DEF(SetHl_ttscrollpane)(ttscrollpane o, ttint hl) {
    ttint ohl = o->hl;
    
    FNSUPER(ttscrollpane)->SetHl((SUPER(ttscrollpane))o, hl);
    
    hl = o->hl;
    
    /* a scrollpane must keep in sync with its scrollbars */
    if (hl != ohl && o->bar_y)
	TTSetSize_ttanyscroll((tt_obj)o->bar_y->id, hl);

    return TT_TRUE;
}
static void DEF(AddTo_ttscrollpane)(ttscrollpane o, ttvisible parent, ttany constraint) {
    ttscrollbar sx, sy;
    
    FNSUPER(ttscrollpane)->AddTo((SUPER(ttscrollpane))o, parent, constraint);
    
    if ((sx = o->bar_x))
	TTAdd_ttvisible((tt_obj)parent->id, (tt_obj)sx->id, ttborderlayout_constraint_south);
    if ((sy = o->bar_y))
	TTAdd_ttvisible((tt_obj)parent->id, (tt_obj)sy->id, ttborderlayout_constraint_east);
}
static void DEF(Remove_ttscrollpane)(ttscrollpane o) {
    ttscrollbar sx, sy;
    
    FNSUPER(ttscrollpane)->Remove((SUPER(ttscrollpane))o);
    
    if ((sx = o->bar_x))
	TTRemove_ttvisible((tt_obj)sx->id);
    if ((sy = o->bar_y))
	TTRemove_ttvisible((tt_obj)sy->id);
}
    
    
/* FIXME: scrollpanes must listen for children add/remove and recalculate wl,hl */

static ttbyte DEF(SetBarX_ttscrollpane)(ttscrollpane o, ttscrollbar b) {
    ttscrollbar ob = o->bar_x;
    
    if (b != ob && (!b || (b->orientation == ttanyscroll_orientation_x))) {

	if (ob)
	    ob->scrollpane = (ttscrollpane)0;
	    
	if (b) {
	    if (b->scrollpane)
		TTSetBarX_ttscrollpane((tt_obj)b->scrollpane->id, NULL);
	    b->scrollpane = o;
	}

	FIRE_EVENT(o->bar_x = b, o, ttscrollpane_bar_x, (ttany)(opaque)b, (ttany)(opaque)ob);
	
	if (b) {
	    /* FIXME: we should fire events too */
	    if (b->parent != o->parent) {
		b->Class->Remove(b);
		b->Class->AddTo(b, o->parent, ttborderlayout_constraint_east);
	    }
	    b->Class->SetXY(b, 0, o->h);
	    b->Class->Recalculate(b, o->wl, TT_MIN_ttint, o->w, o->xl);
	}
    }
    return TT_TRUE;
}
static ttbyte DEF(SetBarY_ttscrollpane)(ttscrollpane o, ttscrollbar b) {
    ttscrollbar ob = o->bar_y;
    
    if (b != ob && (!b || (b->orientation == ttanyscroll_orientation_y))) {

	if (ob)
	    ob->scrollpane = (ttscrollpane)0;
	    
	if (b) {
	    if (b->scrollpane)
		TTSetBarY_ttscrollpane((tt_obj)b->scrollpane->id, NULL);
	    b->scrollpane = o;
	}

	FIRE_EVENT(o->bar_y = b, o, ttscrollpane_bar_y, (ttany)(opaque)b, (ttany)(opaque)ob);
	
	if (b) {
	    /* FIXME: we should fire events too */
	    if (b->parent != o->parent) {
		b->Class->Remove(b);
		b->Class->AddTo(b, o->parent, ttborderlayout_constraint_south);
	    }
	    b->Class->SetXY(b, o->w, 0);
	    b->Class->Recalculate(b, o->hl, TT_MIN_ttint, o->h, o->yl);
	}
    }
    return TT_TRUE;
}

/* ttscroller */
static NEW(ttscroller) {
    ttscrollpane sp;
    
    if ((o = NEWSUPER(ttscroller))) {
	o->Class->SetLayout(o, (ttlayout)TNEW(ttborderlayout));
	
	if (o->layout && (o->scrollpane = sp = Create_ttscrollpane(o->w - 1, o->h - 1))) {
	    
	    sp->Class->AddTo(sp, (ttvisible)o, ttborderlayout_constraint_center);

	    return o;
	}
	TDEL(o);
    }
    return NULL;
}
static DEL(ttscroller) {
    if (o) {
	if (o->scrollpane) {
	    TDEL(o->scrollpane);
	    o->scrollpane = (ttscrollpane)0;
	}
	DELSUPER(ttscroller);
    }
}
static ttbyte DEF(SetWH_ttscroller)(ttscroller o, ttshort w, ttshort h) {
    ttscrollpane sp;
    ttshort ow = o->w, oh = o->h;
    
    FNSUPER(ttscroller)->SetWH((SUPER(ttscroller))o, w, h);
    
    w = o->w; h = o->h;
    
    /* a scroller must keep its scrollpane in sync */
    if (w != ow || h != oh) {
	if ((sp = o->scrollpane))
	    TTSetWH_ttwidget((tt_obj)sp->id, w - 1, h - 1);
    }
    return TT_TRUE;
}

/* ttwindow */
static NEW(ttwindow) {
    if ((o = NEWSUPER(ttwindow))) {
	o->title_len = 0;
	o->title = NULL;
    }
    return o;
}
static DEL(ttwindow) {
    if (o) {
	if (o->title) {
	    TTFreeMem(o->title);
	    o->title = NULL;
	}
	DELSUPER(ttwindow);
    }
}
static ttbyte DEF(SetTitle_ttwindow)(ttwindow o, TT_ARG_READ ttbyte *title) {
    ttbyte *_title = NULL;
    ttopaque title_len;
    
    if (!title || ((title_len = TTLenStr(title)),
		   (_title = TTCloneStrL(title, title_len + 1)))) {
	if (o->title)
	    TTFreeMem(o->title);
	o->title = _title;
	o->title_len = title_len;
	
	return TT_TRUE;
    }
    return TT_FALSE;
}

/* ttframe */
static NEW(ttframe) {
    if ((o = NEWSUPER(ttframe))) {
	o->menubar = NULL;
    }
    return o;
}
static BUILD(ttframe) {
    /* ttframe are non-visible when created */
    o->vflags &= ~ttvisible_vflags_visible;
    o->Class->AddTo(o, (ttvisible)TClass_ttnative->GetRoot(), ttlayout_constraint_root);
    return o;
}
static ttbyte DEF(SetMenubar_ttframe)(ttframe o, ttmenubar m) {
    o->menubar = m;
    return TT_TRUE;
}

/* ttmenuitem */


/* ttcheckmenuitem */


/* ttradiomenuitem */


/* ttmenuwindow */


/* ttmenu */

static NEW(ttmenu) {
    if ((o = NEWSUPER(ttmenu))) {
	o->menubar = (ttmenubar)0;
    }
    return o;
}

/* ttmenubar */

static DEL(ttmenubar) {
    if (o) {
	if (TTD.Menubar == o)
	    TTD.Menubar = (ttmenubar)0;
	DELSUPER(ttmenubar);
    }
}

/* ttanytext */

static NEW(ttanytext) {
    if ((o = NEWSUPER(ttanytext))) {
	o->text = NULL;
	o->text_len = 0;
    }
    return o;
}


/* tttextfield */


/* tttextarea */


/* tttheme */

static BUILD(tttheme) {
    /* FIXME finish this */
    return o;
}
static DEL(tttheme) {
    if (o) {
	if (TTD.Theme == o)
	    TTD.Theme = (tttheme)0;
	DELSUPER(tttheme);
    }
}


/* ttapplication */
static NEW(ttapplication) {
    if ((o = NEWSUPER(ttapplication))) {
	o->name = (ttbyte *)0;
    }
    return o;
}

static DEL(ttapplication) {
    if (o) {
	if (TTD.Application == o)
	    TTD.Application = (ttapplication)0;
	if (o->name) {
	    TTFreeMem(o->name);
	    o->name = (ttbyte *)0;
	}
	DELSUPER(ttapplication);
    }
}


/* common stuff: */


static byte HWDEF(Sync)(void) {
    return TT_TRUE;
}
static byte HWDEF(Flush)(void) {
    return TT_TRUE;
}
static byte HWDEF(TimidFlush)(void) {
    return TT_TRUE;
}
static byte HWDEF(MainLoopOnce)(ttbyte wait) {
    return TT_TRUE;
}
static void HWDEF(DeleteCallback)(ttcallback o) {
}
static void HWDEF(Close)(void) {
}
static int HWDEF(ConnectionFd)(void) {
    return TT_NOFD;
}
static ttuint HWDEF(GetErrno)(void) {
    return 0;
}
static ttuint HWDEF(GetErrnoDetail)(void) {
    return 0;
}
static TT_CONST byte *HWDEF(StrError)(ttuint E) {
    return "";
}
static TT_CONST byte *HWDEF(StrErrorDetail)(ttuint E, ttuint S) {
    return "";
}


ttclasses _TT_null_InitHW(tthw *HW) {
    *HW = &null_TTClasses.HW;
    return &null_TTClasses;
}


DECL_AFTER

