package me.desht.scrollingmenusign.views.icon; import me.desht.dhutils.Debugger; import me.desht.dhutils.MiscUtil; import me.desht.scrollingmenusign.SMSException; import me.desht.scrollingmenusign.SMSMenuItem; import me.desht.scrollingmenusign.ScrollingMenuSign; import me.desht.scrollingmenusign.enums.ViewJustification; import me.desht.scrollingmenusign.util.SMSUtil; import me.desht.scrollingmenusign.views.SMSInventoryView; import me.desht.scrollingmenusign.views.SMSPopup; import me.desht.scrollingmenusign.views.SMSView; import org.apache.commons.lang.StringUtils; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.HandlerList; import org.bukkit.event.Listener; import org.bukkit.event.inventory.ClickType; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryCloseEvent; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.metadata.FixedMetadataValue; import org.bukkit.metadata.MetadataValue; import java.util.Collections; public class IconMenu implements Listener, SMSPopup { private static final int INVENTORY_WIDTH = 9; private static final int MAX_INVENTORY_ROWS = 6; private static final String SMS_CLOSING_ON_COMMAND = "SMS_Closing_On_Command"; private static final int MAX_TITLE_LENGTH = 32; private final SMSInventoryView view; private final Player player; private int size = 0; private ItemStack[] optionIcons; private String[] optionNames; private boolean popped = false; public IconMenu(SMSInventoryView view, Player player) { this.view = view; this.player = player; Debugger.getInstance().debug("icon menu: register events: " + this + " view=" + view.getName() + ", player=" + player.getName()); Bukkit.getPluginManager().registerEvents(this, ScrollingMenuSign.getInstance()); } @Override public SMSView getView() { return view; } @Override public boolean isPoppedUp() { return popped; } @Override public void popup() { if (!isPoppedUp()) { popped = true; if (size == 0 || getView().isDirty(player)) { buildMenu(player); } String title = getAbbreviatedTitle(); Inventory inventory = Bukkit.createInventory(player, size, title); for (int i = 0; i < size; i++) { inventory.setItem(i, optionIcons[i]); } player.openInventory(inventory); getView().setDirty(player, false); } } @Override public void popdown() { if (isPoppedUp()) { player.closeInventory(); popped = false; } } @Override public Player getPlayer() { return player; } @Override public void repaint() { getView().setDirty(true); if (isPoppedUp()) { popdown(); popup(); } } private static int roundUp(int n, int nearestMultiple) { return n + nearestMultiple - 1 - (n - 1) % nearestMultiple; } private void buildMenu(Player player) { final int width = (Integer) getView().getAttribute(SMSInventoryView.WIDTH); final int spacing = (Integer) getView().getAttribute(SMSInventoryView.SPACING); final int nItems = getView().getActiveMenuItemCount(player); ItemStack defIcon = ScrollingMenuSign.getInstance().getConfigCache().getDefaultInventoryViewIcon(); final int iconsPerRow = roundUp(width, spacing) / spacing; final int maxRows = roundUp(nItems, iconsPerRow) / iconsPerRow; size = INVENTORY_WIDTH * Math.min(MAX_INVENTORY_ROWS, maxRows); optionIcons = new ItemStack[size]; optionNames = new String[size]; int row = 0; int col = 0; int rowWidth = Math.min(nItems, iconsPerRow); rowWidth += (spacing - 1) * (rowWidth - 1); int xOff = getXOffset(rowWidth); for (int i = 0; i < nItems; i++) { int pos = row * INVENTORY_WIDTH + xOff + col; SMSMenuItem menuItem = getView().getActiveMenuItemAt(player, i + 1); boolean hasPermission = menuItem.hasPermission(player); ItemStack icon = hasPermission && menuItem.hasIcon() ? menuItem.getIcon() : defIcon.clone(); String label = getView().doVariableSubstitutions(player, getView().getActiveItemLabel(player, i + 1)); ItemMeta im = icon.getItemMeta(); if (im != null) { // could be null if AIR is used as the icon material (spacers) im.setDisplayName(ChatColor.RESET + label); im.setLore(hasPermission ? view.doVariableSubstitutions(player, menuItem.getLoreAsList()) : Collections.<String>emptyList()); icon.setItemMeta(im); } optionIcons[pos] = icon; optionNames[pos] = menuItem.getLabel(); col += spacing; if (i % iconsPerRow == iconsPerRow - 1) { row++; rowWidth = Math.min(nItems - (i + 1), iconsPerRow); rowWidth += (spacing - 1) * (rowWidth - 1); xOff = getXOffset(rowWidth); col = 0; if (row >= MAX_INVENTORY_ROWS) { break; } } } Debugger.getInstance().debug("built icon menu inventory for " + player.getDisplayName() + ": " + size + " slots"); } private int getXOffset(int width) { ViewJustification ij = view.getItemJustification(); switch (ij) { case LEFT: return 0; case RIGHT: return INVENTORY_WIDTH - width; case CENTER: return (INVENTORY_WIDTH - width) / 2; default: throw new IllegalArgumentException("unknown justification: " + ij); } } @EventHandler(priority = EventPriority.HIGHEST) void onInventoryClick(InventoryClickEvent event) { if (!(event.getWhoClicked() instanceof Player) || !(event.getWhoClicked().equals(player))) { return; } String menuTitle = getAbbreviatedTitle(); if (isPoppedUp() && event.getInventory().getTitle().equals(menuTitle)) { Debugger.getInstance().debug("InventoryClickEvent: player = " + player.getDisplayName() + ", view = " + getView().getName() + ", inventory name = " + event.getInventory().getTitle() + ", icon menu = " + this); event.setCancelled(true); int slot = event.getRawSlot(); if (slot >= 0 && slot < size && optionNames[slot] != null) { OptionClickEvent optionEvent = new OptionClickEvent(player, optionNames[slot], event.getClick()); try { view.onOptionClick(optionEvent); } catch (SMSException e) { MiscUtil.errorMessage(player, e.getMessage()); } if (optionEvent.willClose()) { player.setMetadata(SMS_CLOSING_ON_COMMAND, new FixedMetadataValue(ScrollingMenuSign.getInstance(), true)); Bukkit.getScheduler().runTask(ScrollingMenuSign.getInstance(), new Runnable() { @Override public void run() { player.closeInventory(); } }); } if (optionEvent.willDestroy()) { destroy(); } } } } @EventHandler(priority = EventPriority.MONITOR) void onInventoryClose(InventoryCloseEvent event) { if (!(event.getPlayer() instanceof Player) || !(event.getPlayer().equals(this.player))) { return; } String menuTitle = getAbbreviatedTitle(); if (isPoppedUp() && event.getInventory().getTitle().equals(menuTitle)) { Debugger.getInstance().debug("InventoryCloseEvent: player = " + player.getDisplayName() + ", view = " + getView().getName() + ", inventory name = " + event.getInventory().getTitle() + ", icon menu = " + this); if ((Boolean) getView().getAttribute(SMSInventoryView.NO_ESCAPE) && !isClosingOnCommand(player)) { // force the view to be re-shown Bukkit.getScheduler().runTask(ScrollingMenuSign.getInstance(), new Runnable() { @Override public void run() { popup(); } }); } player.removeMetadata(SMS_CLOSING_ON_COMMAND, ScrollingMenuSign.getInstance()); popped = false; } } private String getAbbreviatedTitle() { return StringUtils.abbreviate(getView().doVariableSubstitutions(player, getView().getActiveMenuTitle(player)), MAX_TITLE_LENGTH); } private boolean isClosingOnCommand(Player p) { for (MetadataValue mv : p.getMetadata(SMS_CLOSING_ON_COMMAND)) { if (mv.getOwningPlugin() == ScrollingMenuSign.getInstance()) { return (Boolean) mv.value(); } } return false; } public void destroy() { Debugger.getInstance().debug("destroying icon menu [" + getPlayer().getName() + "] for view: " + view.getName()); HandlerList.unregisterAll(this); popdown(); } public interface OptionClickEventHandler { public void onOptionClick(OptionClickEvent event); } @Override public String toString() { return "icon menu [player=" + player.getName() + " view=" + view.getName() + " size=" + size + "]"; } public class OptionClickEvent { private final Player player; private final String label; private final ClickType clickType; private boolean close; private boolean destroy; public OptionClickEvent(Player player, String label, ClickType type) { this.player = player; this.label = label; this.close = true; this.destroy = false; this.clickType = type; } public Player getPlayer() { return player; } public String getLabel() { return label; } public boolean willClose() { return close; } public boolean willDestroy() { return destroy; } public ClickType getClickType() { return clickType; } public void setWillClose(boolean close) { this.close = close; } public void setWillDestroy(boolean destroy) { this.destroy = destroy; } } }