mirror of
https://github.com/dyne/FreeJ.git
synced 2026-02-11 15:20:48 +01:00
better optimization flags git-svn-id: svn://dyne.org/rastasoft/freej/freej@622 383723c8-4afa-0310-b8a8-b1afb83214fc
709 lines
18 KiB
C++
709 lines
18 KiB
C++
////////////////////////////////////////////////////////////
|
|
// Flash Plugin and Player
|
|
// Copyright (C) 1998,1999 Olivier Debon
|
|
//
|
|
// This program is free software; you can redistribute it and/or
|
|
// modify it under the terms of the GNU General Public License
|
|
// as published by the Free Software Foundation; either version 2
|
|
// of the License, or (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program; if not, write to the Free Software
|
|
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
//
|
|
///////////////////////////////////////////////////////////////
|
|
// Author : Olivier Debon <odebon@club-internet.fr>
|
|
//
|
|
|
|
#include "swf.h"
|
|
|
|
#ifdef RCSID
|
|
static char *rcsid = "$Id: displaylist.cc,v 1.2 2004/12/22 18:36:41 tgc Exp $";
|
|
#endif
|
|
|
|
#define PRINT 0
|
|
|
|
void deleteButton(FlashMovie *movie, DisplayListEntry *e)
|
|
{
|
|
/* save the focus */
|
|
if (movie->mouse_active == 0 && e->renderState == stateOver) {
|
|
movie->lost_over = (Button *)e->character;
|
|
movie->cur_focus = NULL;
|
|
}
|
|
|
|
if (e == movie->cur_focus) {
|
|
movie->cur_focus = NULL;
|
|
}
|
|
}
|
|
|
|
void addButton(FlashMovie *movie, DisplayListEntry *e)
|
|
{
|
|
if (movie->mouse_active == 0 &&
|
|
movie->cur_focus == NULL &&
|
|
movie->lost_over == (Button *)e->character) {
|
|
/* restore the lost focus */
|
|
e->renderState = stateOver;
|
|
e->oldState = stateOver;
|
|
((Button *)e->character)->updateButtonState(e);
|
|
movie->lost_over = NULL;
|
|
movie->cur_focus = e;
|
|
}
|
|
}
|
|
|
|
DisplayList::DisplayList(FlashMovie *movie)
|
|
{
|
|
list = NULL;
|
|
this->movie = movie;
|
|
bbox.reset();
|
|
isSprite = 0;
|
|
}
|
|
|
|
DisplayList::~DisplayList()
|
|
{
|
|
clearList();
|
|
}
|
|
|
|
void
|
|
DisplayList::clearList()
|
|
{
|
|
DisplayListEntry *del, *e;
|
|
|
|
for(e = list; e;)
|
|
{
|
|
updateBoundingBox(e);
|
|
if (e->character->isButton()) {
|
|
deleteButton(movie,e);
|
|
}
|
|
del = e;
|
|
e = e->next;
|
|
delete del;
|
|
}
|
|
list = 0;
|
|
}
|
|
|
|
DisplayListEntry *
|
|
DisplayList::getList()
|
|
{
|
|
return list;
|
|
}
|
|
|
|
static void bbox(Rect *rect, Matrix *m, long x1, long y1)
|
|
{
|
|
long x,y;
|
|
|
|
x = m->getX(x1,y1);
|
|
y = m->getY(x1,y1);
|
|
if (x < rect->xmin) rect->xmin = x;
|
|
if (x > rect->xmax) rect->xmax = x;
|
|
if (y < rect->ymin) rect->ymin = y;
|
|
if (y > rect->ymax) rect->ymax = y;
|
|
}
|
|
|
|
// Update bb to include boundary, optional reset of bb
|
|
void transformBoundingBox(Rect *bb, Matrix *matrix, Rect *boundary, int reset)
|
|
{
|
|
if (reset) {
|
|
bb->reset();
|
|
}
|
|
|
|
if (boundary->xmin != LONG_MAX && matrix != NULL) {
|
|
bbox(bb, matrix, boundary->xmin, boundary->ymin);
|
|
bbox(bb, matrix, boundary->xmax, boundary->ymin);
|
|
bbox(bb, matrix, boundary->xmin, boundary->ymax);
|
|
bbox(bb, matrix, boundary->xmax, boundary->ymax);
|
|
}
|
|
}
|
|
|
|
void
|
|
DisplayList::placeObject(GraphicDevice *gd,Character *character, long depth, Matrix *matrix, Cxform *cxform, char *name)
|
|
{
|
|
DisplayListEntry *n,*e,*prev;
|
|
|
|
n = new DisplayListEntry;
|
|
if (n == NULL) return;
|
|
|
|
n->depth = depth;
|
|
n->matrix = matrix;
|
|
n->cxform = cxform;
|
|
n->character = character;
|
|
n->instanceName = name;
|
|
n->owner = this;
|
|
|
|
#if 0
|
|
printf("Dl %lx: placeObject: depth=%d character=%d cxform=%p\n",
|
|
this, n->depth,n->character ? n->character->getTagId() : 0, cxform);
|
|
#endif
|
|
|
|
if (character == 0 || matrix == 0 || cxform == 0) {
|
|
for (e = list; e; prev = e, e = e->next) {
|
|
if (e->depth == n->depth) {
|
|
if (character == 0) {
|
|
n->character = e->character;
|
|
}
|
|
if (matrix == 0) {
|
|
n->matrix = e->matrix;
|
|
}
|
|
if (cxform == 0) {
|
|
n->cxform = e->cxform;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (n->character == 0) {
|
|
// Not found !!! Should not happen
|
|
// printf("PlaceObject cannot find character at depth %ld\n", n->depth);
|
|
delete n;
|
|
return;
|
|
}
|
|
|
|
prev = 0;
|
|
for (e = list; e; prev = e, e = e->next)
|
|
{
|
|
if (e->depth == n->depth) {
|
|
if (e->character->isButton()) {
|
|
deleteButton(movie, e);
|
|
}
|
|
|
|
// Do update, object has moved or been resized
|
|
updateBoundingBox(e);
|
|
|
|
// Replace object
|
|
e->depth = n->depth;
|
|
e->matrix = n->matrix;
|
|
e->cxform = n->cxform;
|
|
e->character = n->character;
|
|
/* if it is a button, we must update its state */
|
|
if (e->character->isButton()) {
|
|
movie->buttons_updated = 1;
|
|
addButton(movie, e);
|
|
}
|
|
|
|
updateBoundingBox(e);
|
|
|
|
delete n;
|
|
return;
|
|
}
|
|
if (e->depth > n->depth) break;
|
|
}
|
|
/* new object */
|
|
|
|
/* button instantiation */
|
|
if (n->character->isButton()) {
|
|
n->renderState = stateUp;
|
|
n->oldState = stateUp;
|
|
((Button *)n->character)->updateButtonState(n);
|
|
addButton(movie,n);
|
|
}
|
|
|
|
updateBoundingBox(n);
|
|
|
|
if (prev == 0) {
|
|
// Object comes at first place
|
|
n->next = list;
|
|
list = n;
|
|
} else {
|
|
// Insert object
|
|
n->next = prev->next;
|
|
prev->next = n;
|
|
}
|
|
}
|
|
|
|
|
|
Character *
|
|
DisplayList::removeObject(GraphicDevice *gd,Character *character, long depth)
|
|
{
|
|
DisplayListEntry *e,*prev;
|
|
|
|
// List should not be empty
|
|
if (list == 0) return 0;
|
|
|
|
#if 0
|
|
printf("removeObject: depth=%d character=%d\n",
|
|
depth,character ? character->getTagId() : 0);
|
|
#endif
|
|
|
|
prev = 0;
|
|
for (e = list; e; prev = e, e = e->next) {
|
|
if (e->depth == depth) {
|
|
if (prev) {
|
|
prev->next = e->next;
|
|
} else {
|
|
list = e->next;
|
|
}
|
|
if (character == 0) {
|
|
character = e->character;
|
|
}
|
|
if (e->character->isButton()) {
|
|
deleteButton(movie, e);
|
|
}
|
|
if (e->character->isSprite()) {
|
|
((Sprite*)e->character)->reset();
|
|
}
|
|
|
|
updateBoundingBox(e);
|
|
|
|
delete e;
|
|
return character;
|
|
}
|
|
}
|
|
return 0; // Should not happen
|
|
}
|
|
|
|
void
|
|
DisplayList::updateBoundingBox(DisplayListEntry *e)
|
|
{
|
|
Rect rect;
|
|
|
|
//rect.reset();
|
|
e->character->getBoundingBox(&rect,e);
|
|
transformBoundingBox(&this->bbox, e->matrix, &rect, 0);
|
|
}
|
|
|
|
int
|
|
DisplayList::updateSprites()
|
|
{
|
|
Sprite *sprite;
|
|
DisplayListEntry *e;
|
|
int refresh = 0;
|
|
|
|
for (e = this->list; e != NULL; e = e->next) {
|
|
if (e->character->isButton() && e->buttonCharacter) {
|
|
if (e->buttonCharacter->isSprite()) {
|
|
Matrix mat;
|
|
|
|
sprite = (Sprite *)e->buttonCharacter;
|
|
refresh |= sprite->program->dl->updateSprites();
|
|
refresh |= sprite->program->nestedMovie(this->movie->gd, e->matrix, e->cxform);
|
|
mat = (*e->matrix) * e->buttonMatrix;
|
|
transformBoundingBox(&this->bbox, &mat,
|
|
&(sprite->program->dl->bbox),
|
|
0);
|
|
}
|
|
}
|
|
if (e->character->isSprite()) {
|
|
sprite = (Sprite *)e->character;
|
|
refresh |= sprite->program->dl->updateSprites();
|
|
refresh |= sprite->program->nestedMovie(this->movie->gd, e->matrix, e->cxform);
|
|
transformBoundingBox(&this->bbox, e->matrix,
|
|
&(sprite->program->dl->bbox),
|
|
0);
|
|
}
|
|
}
|
|
return refresh;
|
|
}
|
|
|
|
/* Function can return either 0,1 or 2
|
|
0: Nothing match, continue
|
|
1: Something matches, but continue searching
|
|
2: Something matches, but stop searching
|
|
*/
|
|
|
|
static int exploreButtons1(Program *prg, void *opaque,
|
|
ExploreButtonFunc func)
|
|
{
|
|
DisplayListEntry *e;
|
|
int ret, ret2 = 0;
|
|
|
|
for(e=prg->dl->list; e != NULL; e = e->next) {
|
|
if (e->character == NULL) continue;
|
|
if (e->character->isButton()) {
|
|
ret = func(opaque,prg,e);
|
|
if (ret == 2) return ret; // Func asks to return at once !!!
|
|
if (ret) ret2 = 1;
|
|
}
|
|
if (e->character->isSprite()) {
|
|
ret = exploreButtons1(((Sprite *)e->character)->program,
|
|
opaque,func);
|
|
if (ret == 2) return ret; // Func asks to return at once !!!
|
|
if (ret) ret2 = 1;
|
|
}
|
|
}
|
|
return ret2;
|
|
}
|
|
|
|
int exploreButtons(FlashMovie *movie, void *opaque, ExploreButtonFunc func)
|
|
{
|
|
CInputScript *script;
|
|
int ret;
|
|
|
|
script = movie->main;
|
|
while (script != NULL) {
|
|
if (script->program) {
|
|
ret = exploreButtons1(script->program, opaque, func);
|
|
if (ret) return ret;
|
|
}
|
|
script = script->next;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
typedef struct {
|
|
long x,y;
|
|
int hit;
|
|
DisplayListEntry *bhit;
|
|
} HitTable;
|
|
|
|
static void button_hit_func(void *id, long y, long start, long end)
|
|
{
|
|
HitTable *h = (HitTable *) id;
|
|
if ( y == h->y && (h->x >= start && h->x < end) )
|
|
h->hit = 1;
|
|
}
|
|
|
|
typedef struct {
|
|
FlashMovie *movie;
|
|
DisplayListEntry *bhit;
|
|
} ButtonHit;
|
|
|
|
static int button_hit(void *opaque, Program *prg, DisplayListEntry *e)
|
|
{
|
|
ButtonHit *h = (ButtonHit *) opaque;
|
|
HitTable hit_table;
|
|
FlashMovie *movie = h->movie;
|
|
Rect bb,boundary;
|
|
Matrix mat;
|
|
ButtonState save;
|
|
|
|
hit_table.x = movie->mouse_x;
|
|
hit_table.y = movie->mouse_y / FRAC;
|
|
hit_table.hit = 0;
|
|
|
|
// Compute the bounding box in screen coordinates
|
|
save = e->renderState;
|
|
e->renderState = stateHitTest;
|
|
e->character->getBoundingBox(&boundary,e);
|
|
e->renderState = save;
|
|
mat = (*movie->gd->adjust) * e->renderMatrix;
|
|
transformBoundingBox(&bb, &mat, &boundary, 1);
|
|
// Check if mouse is within bb
|
|
if (movie->mouse_x < bb.xmin) return 0;
|
|
if (movie->mouse_x > bb.xmax) return 0;
|
|
if (movie->mouse_y < bb.ymin) return 0;
|
|
if (movie->mouse_y > bb.ymax) return 0;
|
|
|
|
e->character->getRegion(movie->gd, &e->renderMatrix,
|
|
&hit_table, button_hit_func);
|
|
|
|
if (hit_table.hit) {
|
|
h->bhit = e;
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static int button_reset(void *opaque, Program *prg, DisplayListEntry *e)
|
|
{
|
|
if (e->renderState != stateUp) {
|
|
e->owner->updateBoundingBox(e);
|
|
e->oldState = e->renderState;
|
|
e->renderState = stateUp;
|
|
((Button *)e->character)->updateButtonState(e);
|
|
e->owner->updateBoundingBox(e);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* update the button states according to the current mouse state & return the list of actions */
|
|
void
|
|
DisplayList::updateButtons(FlashMovie *movie)
|
|
{
|
|
DisplayListEntry *bhit;
|
|
ButtonHit h;
|
|
|
|
if (movie->mouse_active) {
|
|
|
|
h.bhit = NULL;
|
|
h.movie = movie;
|
|
|
|
exploreButtons(movie, &h, button_hit);
|
|
|
|
bhit = h.bhit;
|
|
|
|
/* set every button to not hit */
|
|
exploreButtons(movie, NULL, button_reset);
|
|
|
|
if (bhit) {
|
|
ButtonState state;
|
|
|
|
if (movie->button_pressed) {
|
|
state = stateDown;
|
|
} else {
|
|
state = stateOver;
|
|
}
|
|
if (state != bhit->renderState) {
|
|
bhit->owner->updateBoundingBox(bhit);
|
|
bhit->renderState = state;
|
|
((Button *)bhit->character)->updateButtonState(bhit);
|
|
bhit->owner->updateBoundingBox(bhit);
|
|
movie->cur_focus = bhit;
|
|
if (movie->cursorOnOff)
|
|
movie->cursorOnOff(1,movie->cursorOnOffClientData);
|
|
}
|
|
} else {
|
|
if (movie->cursorOnOff)
|
|
movie->cursorOnOff(0,movie->cursorOnOffClientData);
|
|
}
|
|
}
|
|
}
|
|
|
|
typedef struct {
|
|
ActionRecord *action; // Action to do
|
|
Program *prg; // Context program
|
|
} ButtonAction;
|
|
|
|
static int button_action(void *opaque, Program *prg, DisplayListEntry *e)
|
|
{
|
|
ButtonAction *h = (ButtonAction *)opaque;
|
|
static ActionRecord actionRefresh;
|
|
static ActionRecord soundFx;
|
|
Button *b;
|
|
ActionRecord **paction;
|
|
int n;
|
|
|
|
actionRefresh.action = ActionRefresh;
|
|
actionRefresh.next = 0;
|
|
|
|
soundFx.action = ActionPlaySound;
|
|
soundFx.next = &actionRefresh;
|
|
|
|
b = (Button *)e->character;
|
|
|
|
if (e->oldState != e->renderState) {
|
|
|
|
paction = &actionRefresh.next;
|
|
|
|
if (b->conditionList) {
|
|
*paction = b->getActionFromTransition(e->renderState, e->oldState);
|
|
} else if (e->renderState == stateDown) {
|
|
/* if the button is pressed and
|
|
no condition list is defined*/
|
|
*paction = b->actionRecords;
|
|
}
|
|
|
|
switch(e->renderState) {
|
|
case stateUp:
|
|
n = 0;
|
|
break;
|
|
case stateOver:
|
|
n = 1;
|
|
break;
|
|
default:
|
|
/* case stateDown: */
|
|
n = 2;
|
|
break;
|
|
}
|
|
|
|
if (b->sound[n]) {
|
|
soundFx.sound = b->sound[n];
|
|
h->action = &soundFx;
|
|
} else {
|
|
h->action = &actionRefresh;
|
|
}
|
|
|
|
e->oldState = e->renderState;
|
|
|
|
h->prg = prg;
|
|
return 2;
|
|
}
|
|
h->action = 0; // Nothing to do about this
|
|
return 0;
|
|
}
|
|
|
|
int computeActions(FlashMovie *movie, Program **prg, ActionRecord **ar)
|
|
{
|
|
ButtonAction h;
|
|
|
|
h.action = NULL;
|
|
exploreButtons(movie, &h, button_action);
|
|
if (h.action) {
|
|
*prg = h.prg;
|
|
*ar = h.action;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#define FOCUS_ZOOM 1.5
|
|
/* in pixels */
|
|
#define FOCUS_SIZE_MIN 50
|
|
#define FOCUS_TRANSLATE 15
|
|
|
|
int
|
|
DisplayList::render(GraphicDevice *gd, Matrix *render_matrix, Cxform *cxform)
|
|
{
|
|
DisplayListEntry *e,*cur_focus;
|
|
int sprite = 0;
|
|
long n = 0;
|
|
Cxform cxf,*cxf1;
|
|
Rect bb,boundary;
|
|
|
|
cur_focus = NULL;
|
|
|
|
/*
|
|
if (isSprite == 0) {
|
|
if (this->bbox.xmin == LONG_MAX) return 0;
|
|
gd->updateClippingRegion(&this->bbox, render_matrix);
|
|
gd->clearCanvas();
|
|
}
|
|
*/
|
|
|
|
for (e = list; e; e = e->next)
|
|
{
|
|
#if PRINT
|
|
printf("Character %3d @ %3d\n", e->character ? e->character->getTagId() : 0, e->depth);
|
|
#endif
|
|
if (e->character) {
|
|
Matrix mat;
|
|
|
|
if (render_matrix) {
|
|
mat = *render_matrix;
|
|
}
|
|
|
|
if (e->matrix) {
|
|
mat = mat * (*e->matrix);
|
|
}
|
|
|
|
/* fast clipping */
|
|
// If object boundaries are outside current clip region give up with rendering
|
|
e->character->getBoundingBox(&boundary,e);
|
|
if (boundary.xmin != LONG_MAX) {
|
|
Matrix tmat;
|
|
|
|
tmat = (*gd->adjust) * mat;
|
|
transformBoundingBox(&bb, &tmat, &boundary, 1);
|
|
|
|
bb.xmin = bb.xmin >> FRAC_BITS;
|
|
bb.ymin = bb.ymin >> FRAC_BITS;
|
|
bb.xmax = (bb.xmax + FRAC - 1) >> FRAC_BITS;
|
|
bb.ymax = (bb.ymax + FRAC - 1) >> FRAC_BITS;
|
|
|
|
if (bb.xmin >= gd->clip_rect.xmax ||
|
|
bb.xmax <= gd->clip_rect.xmin ||
|
|
bb.ymin >= gd->clip_rect.ymax ||
|
|
bb.ymax <= gd->clip_rect.ymin) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (cxform == NULL) {
|
|
cxf1 = e->cxform;
|
|
}
|
|
else if (e->cxform == NULL) {
|
|
cxf1 = cxform;
|
|
}
|
|
else {
|
|
cxf1 = &cxf;
|
|
cxf.ra = cxform->ra * e->cxform->ra;
|
|
cxf.ga = cxform->ga * e->cxform->ga;
|
|
cxf.ba = cxform->ba * e->cxform->ba;
|
|
cxf.aa = cxform->aa * e->cxform->aa;
|
|
|
|
cxf.rb = (long)(cxform->ra * e->cxform->rb + cxform->rb);
|
|
cxf.gb = (long)(cxform->ga * e->cxform->gb + cxform->gb);
|
|
cxf.bb = (long)(cxform->ba * e->cxform->bb + cxform->bb);
|
|
cxf.ab = (long)(cxform->aa * e->cxform->ab + cxform->ab);
|
|
}
|
|
|
|
if (e->character->isButton()) {
|
|
Button *b = (Button *) e->character;
|
|
|
|
e->renderMatrix = mat;
|
|
|
|
if (e->renderState != stateUp && movie->mouse_active == 0) {
|
|
cur_focus = e;
|
|
((Button *)e->character)->updateButtonState(e);
|
|
}
|
|
|
|
if (b->execute(gd, &mat, cxf1, e->renderState)) {
|
|
sprite = 1;
|
|
}
|
|
} else {
|
|
if (e->character->execute(gd, &mat, cxf1)) {
|
|
sprite = 1;
|
|
}
|
|
}
|
|
|
|
n++;
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
{
|
|
/* display the bounding box (debug) */
|
|
Matrix tmat;
|
|
long x1,x2,y1,y2;
|
|
Color white;
|
|
|
|
white.red = 255;
|
|
white.green = white.blue = 0;
|
|
gd->setForegroundColor(white);
|
|
|
|
if (render_matrix) {
|
|
tmat = (*gd->adjust) * (*render_matrix);
|
|
} else {
|
|
tmat = *gd->adjust;
|
|
}
|
|
x1 = bbox.xmin;
|
|
y1 = bbox.ymin;
|
|
x2 = bbox.xmax;
|
|
y2 = bbox.ymax;
|
|
gd->drawLine(tmat.getX(x1,y1),tmat.getY(x1,y1),tmat.getX(x2,y1),tmat.getY(x2,y1),10*FRAC);
|
|
gd->drawLine(tmat.getX(x2,y1),tmat.getY(x2,y1),tmat.getX(x2,y2),tmat.getY(x2,y2),10*FRAC);
|
|
gd->drawLine(tmat.getX(x2,y2),tmat.getY(x2,y2),tmat.getX(x1,y2),tmat.getY(x1,y2),10*FRAC);
|
|
gd->drawLine(tmat.getX(x1,y2),tmat.getY(x1,y2),tmat.getX(x1,y1),tmat.getY(x1,y1),10*FRAC);
|
|
bbox.print();
|
|
}
|
|
#endif
|
|
|
|
// Reset clipping zone
|
|
bbox.reset();
|
|
|
|
return sprite;
|
|
}
|
|
|
|
void
|
|
DisplayList::getBoundary(Rect *bb)
|
|
{
|
|
DisplayListEntry *e;
|
|
Rect boundary;
|
|
|
|
bb->reset();
|
|
for (e = list; e; e = e->next)
|
|
{
|
|
if (e->character) {
|
|
e->character->getBoundingBox(&boundary,e);
|
|
transformBoundingBox(bb, e->matrix, &boundary, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
extern "C" {
|
|
|
|
void dump_buttons(FlashHandle flashHandle)
|
|
{
|
|
#if 0
|
|
Rect rect;
|
|
DisplayListEntry *e;
|
|
FlashMovie *movie;
|
|
|
|
movie = (FlashMovie *)flashHandle;
|
|
|
|
for (e = movie->first_button; e; e = e->next_button) {
|
|
computeBBox(movie,&rect,e);
|
|
printf("button: id=%d pos=%d %d %d %d\n",
|
|
e->character->getTagId(),
|
|
rect.xmin, rect.ymin, rect.xmax, rect.ymax);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
}
|