/* $Id: Item2DView.java,v 1.32 2011/01/14 14:27:55 kiheru Exp $ */
/***************************************************************************
* (C) Copyright 2003-2010 - Stendhal *
***************************************************************************
***************************************************************************
* *
* 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. *
* *
***************************************************************************/
package games.stendhal.client.gui.j2d.entity;
import games.stendhal.client.IGameScreen;
import games.stendhal.client.entity.ActionType;
import games.stendhal.client.entity.IEntity;
import games.stendhal.client.entity.Inspector;
import games.stendhal.client.entity.Item;
import games.stendhal.client.gui.InternalWindow;
import games.stendhal.client.gui.InternalWindow.CloseListener;
import games.stendhal.client.gui.SlotWindow;
import games.stendhal.client.gui.styled.cursor.StendhalCursor;
import games.stendhal.client.sprite.Sprite;
import games.stendhal.client.sprite.SpriteStore;
import java.util.List;
import javax.swing.SwingUtilities;
import marauroa.common.game.RPSlot;
import org.apache.log4j.Logger;
/**
* The 2D view of an item.
*/
class Item2DView extends Entity2DView {
/**
* Log4J.
*/
private static final Logger logger = Logger.getLogger(Item2DView.class);
/** Window for showing the slot contents, if any */
private volatile SlotWindow slotWindow;
/** Width of the slot window */
private int slotWindowWidth;
/** height of the slot window */
private int slotWindowHeight;
/** The slot content inspector. */
private Inspector inspector;
//
// Entity2DView
//
/**
* Build the visual representation of this entity.
*/
@Override
protected void buildRepresentation(IEntity entity) {
final SpriteStore store = SpriteStore.get();
Sprite sprite = store.getSprite(translate(getClassResourcePath()));
/*
* Items are always 1x1 (they need to fit in entity slots). Extra
* columns are animation. Extra rows are ignored.
*/
final int width = sprite.getWidth();
if (width > IGameScreen.SIZE_UNIT_PIXELS) {
sprite = store.getAnimatedSprite(sprite, 0, 0, width
/ IGameScreen.SIZE_UNIT_PIXELS,
IGameScreen.SIZE_UNIT_PIXELS, IGameScreen.SIZE_UNIT_PIXELS,
100);
} else if (sprite.getHeight() > IGameScreen.SIZE_UNIT_PIXELS) {
sprite = store.getTile(sprite, 0, 0, IGameScreen.SIZE_UNIT_PIXELS,
IGameScreen.SIZE_UNIT_PIXELS);
logger.warn("Multi-row item image for: " + getClassResourcePath());
}
setSprite(sprite);
}
/**
* Build a list of entity specific actions. <strong>NOTE: The first entry
* should be the default.</strong>
*
* @param list
* The list to populate.
*/
@Override
protected void buildActions(final List<String> list) {
if (getContent() != null) {
list.add(ActionType.INSPECT.getRepresentation());
}
super.buildActions(list);
}
/**
* Determines on top of which other entities this entity should be drawn.
* Entities with a high Z index will be drawn on top of ones with a lower Z
* index.
*
* Also, players can only interact with the topmost entity.
*
* @return The drawing index.
*/
@Override
public int getZIndex() {
return 7000;
}
/**
* Translate a resource name into it's sprite image path.
*
* @param name
* The resource name.
*
* @return The full resource name.
*/
@Override
protected String translate(final String name) {
return "data/sprites/items/" + name + ".png";
}
//
// EntityChangeListener
//
/**
* An entity was changed.
*
* @param entity
* The entity that was changed.
* @param property
* The property identifier.
*/
@Override
public void entityChanged(final IEntity entity, final Object property) {
super.entityChanged(entity, property);
if (property == IEntity.PROP_CLASS) {
representationChanged = true;
}
}
//
// EntityView
//
/**
* Determine if this entity can be moved (e.g. via dragging).
*
* @return <code>true</code> if the entity is movable.
*/
@Override
public boolean isMovable() {
return true;
}
/**
* Perform the default action.
*/
@Override
public void onAction() {
onAction(ActionType.USE);
}
@Override
public boolean onHarmlessAction() {
if (getContent() != null) {
onAction(ActionType.INSPECT);
return true;
}
return false;
}
/**
* Set the content inspector for this entity.
*
* @param inspector
* The inspector.
*/
@Override
public void setInspector(final Inspector inspector) {
this.inspector = inspector;
}
/**
* Perform an action.
*
* @param at
* The action.
*/
@Override
public void onAction(final ActionType at) {
switch (at) {
case USE:
/*
* Send use action even for released items, if they are in a slot.
* Those get released when the slot contents change, and a new view
* is created. Users expect to be able to use multiple double clicks
* or using a menu created previously for the item.
*/
if (!isReleased() || !entity.isOnGround()) {
at.send(at.fillTargetInfo(entity.getRPObject()));
}
break;
case INSPECT:
inspect();
break;
default:
super.onAction(at);
break;
}
}
/**
* Inspect the item. Show the slot contents.
*/
private void inspect() {
RPSlot slot = getContent();
if (slotWindowWidth == 0) {
calculateWindowProportions(slot.getCapacity());
}
boolean addListener = slotWindow == null;
slotWindow = inspector.inspectMe(entity, slot,
slotWindow, slotWindowWidth, slotWindowHeight);
SlotWindow window = slotWindow;
/*
* Register a listener for window closing so that we can
* drop the reference to the closed window and let the
* garbage collector claim it.
*/
if (addListener && (window != null)) {
window.addCloseListener(new CloseListener() {
public void windowClosed(InternalWindow window) {
slotWindow = null;
}
});
}
/*
* In case the view got released while the window was created and
* added, and before the main thread was aware that there's a window
* to be closed, close it now. (onAction is called from the event
* dispatch thread).
*/
if (isReleased()) {
if (window != null) {
window.close();
}
}
}
/**
* Get the content slot.
*
* @return Content slot or <code>null</code> if the item has none or it's
* not accessible.
*/
private RPSlot getContent() {
return ((Item) entity).getContent();
}
/**
* Find out dimensions for a somewhat square slot window.
*
* @param slots number of slots in the window
*/
private void calculateWindowProportions(final int slots) {
int width = (int) Math.sqrt(slots);
while (slots % width != 0) {
width--;
if (width <= 0) {
logger.error("Failed to decide dimensions for slot window. slots = " + slots);
width = 1;
}
}
slotWindowWidth = width;
slotWindowHeight = slots / width;
}
@Override
public void release() {
final SlotWindow window = slotWindow;
if (window != null) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
window.close();
}
});
}
super.release();
}
@Override
public StendhalCursor getCursor() {
return StendhalCursor.NORMAL;
}
}