package me.desht.scrollingmenusign.views;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Observable;
import java.util.UUID;
import me.desht.dhutils.ConfigurationManager;
import me.desht.dhutils.Debugger;
import me.desht.dhutils.LogUtils;
import me.desht.dhutils.PermissionUtils;
import me.desht.scrollingmenusign.SMSException;
import me.desht.scrollingmenusign.SMSMenu;
import me.desht.scrollingmenusign.ScrollingMenuSign;
import me.desht.scrollingmenusign.spout.HexColor;
import me.desht.scrollingmenusign.spout.SMSSpoutKeyMap;
import me.desht.scrollingmenusign.spout.SpoutViewPopup;
import me.desht.scrollingmenusign.spout.TextEntryPopup;
import me.desht.scrollingmenusign.views.action.ViewUpdateAction;
import org.bukkit.Bukkit;
import org.bukkit.configuration.Configuration;
import org.bukkit.entity.Player;
import org.getspout.spoutapi.SpoutManager;
import org.getspout.spoutapi.gui.PopupScreen;
import org.getspout.spoutapi.player.SpoutPlayer;
/**
* This view draws menus on a popped-up Spout view.
*/
public class SMSSpoutView extends SMSScrollableView implements PoppableView {
// attributes
public static final String AUTOPOPDOWN = "autopopdown";
public static final String SPOUTKEYS = "spoutkeys";
public static final String BACKGROUND = "background";
public static final String ALPHA = "alpha";
public static final String TEXTURE = "texture";
// list of all popups which have been created for this view, keyed by player ID
private final Map<UUID, SpoutViewPopup> popups = new HashMap<UUID, SpoutViewPopup>();
// map a set of keypresses to the view which handles them
private static final Map<String, String> keyMap = new HashMap<String, String>();
/**
* Construct a new SMSSPoutView object
*
* @param name The view name
* @param menu The menu to attach the object to
* @throws SMSException
*/
public SMSSpoutView(String name, SMSMenu menu) throws SMSException {
super(name, menu);
setWrap(false);
if (!ScrollingMenuSign.getInstance().isSpoutEnabled()) {
throw new SMSException("Spout view cannot be created - server does not have Spout enabled");
}
Configuration config = ScrollingMenuSign.getInstance().getConfig();
String defColor = config.getString("sms.spout.list_background");
Double defAlpha = config.getDouble("sms.spout.list_alpha");
registerAttribute(SPOUTKEYS, new SMSSpoutKeyMap(), "Key(s) to toggle view visibility");
registerAttribute(AUTOPOPDOWN, true, "Auto-popdown after item click?");
registerAttribute(BACKGROUND, new HexColor(defColor), "Background colour of view");
registerAttribute(ALPHA, defAlpha, "Transparency of view");
registerAttribute(TEXTURE, "", "Image to use as view background");
}
public SMSSpoutView(SMSMenu menu) throws SMSException {
this(null, menu);
}
// NOTE: explicit freeze() and thaw() methods not needed. No new object fields which are not attributes.
/**
* Show the given player's GUI for this view.
*
* @param player The player object
*/
@Override
public void showGUI(Player player) {
SpoutPlayer sp = SpoutManager.getPlayer(player);
if (!sp.isSpoutCraftEnabled())
return;
Debugger.getInstance().debug("showing Spout GUI for " + getName() + " to " + sp.getDisplayName());
if (!popups.containsKey(sp.getUniqueId())) {
// create a new gui for this player
popups.put(sp.getUniqueId(), new SpoutViewPopup(sp, this));
}
SpoutViewPopup gui = popups.get(sp.getUniqueId());
gui.popup();
}
/**
* Hide the given player's GUI for this view.
*
* @param player The player object
*/
@Override
public void hideGUI(Player player) {
SpoutPlayer sp = SpoutManager.getPlayer(player);
if (!sp.isSpoutCraftEnabled())
return;
if (!popups.containsKey(sp.getUniqueId())) {
return;
}
Debugger.getInstance().debug("hiding Spout GUI for " + getName() + " from " + sp.getDisplayName());
popups.get(sp.getUniqueId()).popdown();
// decision: destroy the gui object or not?
// popups.remove(sp.getName());
}
/**
* Check if the given player has an active GUI (for any Spout view, not
* necessarily this one).
*
* @param player the player to check for
* @return true if a GUI is currently popped up, false otherwise
*/
@Override
public boolean hasActiveGUI(Player player) {
final SpoutPlayer sp = SpoutManager.getPlayer(player);
if (!sp.isSpoutCraftEnabled())
return false;
PopupScreen popup = sp.getMainScreen().getActivePopup();
return popup != null && popup instanceof SpoutViewPopup;
}
/**
* Get the active GUI for the given player, if any (for any Spout view, not
* necessarily this one).
*
* @param player the player to check for
* @return the GUI object if one is currently popped up, null otherwise
*/
@Override
public SMSPopup getActiveGUI(Player player) {
final SpoutPlayer sp = SpoutManager.getPlayer(player);
if (!sp.isSpoutCraftEnabled())
return null;
PopupScreen popup = sp.getMainScreen().getActivePopup();
return popup instanceof SpoutViewPopup ? (SMSPopup) popup : null;
}
/**
* Toggle the given player's visibility of the GUI for this view. If a GUI for a different view
* is currently showing, pop that one down, and pop this one up.
*
* @param player The player object
*/
@Override
public void toggleGUI(Player player) {
final SpoutPlayer sp = SpoutManager.getPlayer(player);
if (!sp.isSpoutCraftEnabled())
return;
if (hasActiveGUI(sp)) {
SMSPopup gui = getActiveGUI(sp);
SMSSpoutView poppedUpView = (SMSSpoutView) gui.getView();
if (poppedUpView == this) {
// the player has an active GUI from this view - just pop it down
hideGUI(sp);
} else {
// the player has an active GUI, but it belongs to a different spout view, so pop down
// that one and pop up the GUI for this view
poppedUpView.hideGUI(sp);
// just popping the GUI up immediately doesn't appear to work - we need to defer it by a few ticks
Bukkit.getScheduler().scheduleSyncDelayedTask(ScrollingMenuSign.getInstance(), new Runnable() {
@Override
public void run() {
showGUI(sp);
}
}, 3L);
}
} else {
// no GUI shown right now - just pop this one up
showGUI(sp);
}
}
/* (non-Javadoc)
* @see me.desht.scrollingmenusign.views.SMSScrollableView#update(java.util.Observable, java.lang.Object)
*/
@Override
public void update(Observable menu, Object arg1) {
super.update(menu, arg1);
ViewUpdateAction vu = ViewUpdateAction.getAction(arg1);
for (SpoutViewPopup gui : popups.values()) {
if (vu.getSender() == null || gui.getPlayer().equals(vu.getSender())) {
gui.repaint();
}
}
// although this is a scrollable view, we don't need to do anything if the action was SCROLLED,
// since the Spout list widget handles its own repainting
}
@Override
public void onDeleted(boolean permanent) {
super.onDeleted(permanent);
if (permanent) {
for (Entry<UUID, SpoutViewPopup> e : popups.entrySet()) {
if (e.getValue().isPoppedUp()) {
hideGUI(e.getValue().getPlayer());
}
}
}
}
/* (non-Javadoc)
* @see me.desht.scrollingmenusign.views.SMSView#getType()
*/
@Override
public String getType() {
return "spout";
}
public String toString() {
return "spout - " + popups.size() + " popups created";
}
/* (non-Javadoc)
* @see me.desht.scrollingmenusign.views.SMSView#onConfigurationValidate(me.desht.dhutils.ConfigurationManager, java.lang.String, java.lang.String)
*/
@Override
public Object onConfigurationValidate(ConfigurationManager configurationManager, String attribute, Object oldVal, Object newVal) {
newVal = super.onConfigurationValidate(configurationManager, attribute, oldVal, newVal);
String err = null;
if (attribute.equals(SPOUTKEYS) && !newVal.toString().isEmpty()) {
try {
SMSSpoutKeyMap sp = new SMSSpoutKeyMap(newVal.toString());
if (keyMap.containsKey(sp.toString())) {
String otherView = keyMap.get(sp.toString());
if (ScrollingMenuSign.getInstance().getViewManager().checkForView(otherView)) {
err = sp.toString() + " is already used as the hotkey for another view (" + keyMap.get(sp.toString()) + ")";
}
}
} catch (IllegalArgumentException e) {
throw new SMSException("Invalid key binding: " + newVal);
}
} else if (attribute.equals(ALPHA)) {
try {
double d = (Double) newVal;
if (d < 0.0 || d > 1.0)
err = "Invalid value for alpha channel (must be a floating point number between 0.0 and 1.0)";
} catch (NumberFormatException e) {
err = "Invalid value for alpha channel (must be a floating point number between 0.0 and 1.0)";
}
}
if (err != null) {
throw new SMSException(err);
}
return newVal;
}
/* (non-Javadoc)
* @see me.desht.scrollingmenusign.views.SMSView#onConfigurationChanged(me.desht.dhutils.ConfigurationManager, java.lang.String, java.lang.Object, java.lang.Object)
*/
@Override
public void onConfigurationChanged(ConfigurationManager configurationManager, String attribute, Object oldVal, Object newVal) {
super.onConfigurationChanged(configurationManager, attribute, oldVal, newVal);
if (attribute.equals(SPOUTKEYS)) {
// cache a new stringified key mapping definition for this view
keyMap.remove(oldVal.toString());
if (!newVal.toString().isEmpty()) {
keyMap.put(newVal.toString(), getName());
}
}
}
@Override
public void onExecuted(Player player) {
super.onExecuted(player);
Boolean popdown = (Boolean) getAttribute(AUTOPOPDOWN);
if (popdown != null && popdown) {
hideGUI(SpoutManager.getPlayer(player));
}
}
@Override
public void scrollDown(Player player) {
super.scrollDown(player);
scrollPopup(player);
}
@Override
public void scrollUp(Player player) {
super.scrollUp(player);
scrollPopup(player);
}
private void scrollPopup(Player player) {
if (popups.containsKey(player.getUniqueId())) {
SpoutViewPopup popup = popups.get(player.getUniqueId());
popup.scrollTo(getScrollPos(player));
popup.ignoreNextSelection();
}
}
/**
* A Spout keypress event was received.
*
* @param sp The Spout player who pressed the key(s)
* @param pressed Represents the set of keys currently pressed
* @return True if a spout view was actually popped up or down, false otherwise
*/
public static boolean handleKeypress(SpoutPlayer sp, SMSSpoutKeyMap pressed) {
if (pressed.keysPressed() == 0)
return false;
if (TextEntryPopup.hasActivePopup(sp.getUniqueId())) {
return false;
}
String s = pressed.toString();
ViewManager vm = ScrollingMenuSign.getInstance().getViewManager();
String viewName = keyMap.get(s);
if (viewName != null) {
if (vm.checkForView(viewName)) {
try {
SMSView v = vm.getView(viewName);
if (v instanceof SMSSpoutView) {
if (!PermissionUtils.isAllowedTo(sp, "scrollingmenusign.use.spout"))
return false;
if (!v.hasOwnerPermission(sp))
return false;
((SMSSpoutView) v).toggleGUI(sp);
return true;
} else {
LogUtils.warning("Got non-Spout view " + v.getName() + " for keymap " + s);
}
} catch (SMSException e) {
// shouldn't get here - we checked for the view
}
} else {
// the view was probably deleted - remove the key mapping
keyMap.remove(s);
}
}
return false;
}
/* (non-Javadoc)
* @see me.desht.scrollingmenusign.views.SMSView#clearPlayerForView(org.bukkit.entity.Player)
*/
@Override
public void clearPlayerForView(Player player) {
super.clearPlayerForView(player);
popups.remove(player.getUniqueId());
}
}