/*
* This file is part of NucleusFramework for Bukkit, licensed under the MIT License (MIT).
*
* Copyright (c) JCThePants (www.jcwhatever.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.jcwhatever.nucleus.managed.messaging;
import com.jcwhatever.nucleus.Nucleus;
import com.jcwhatever.nucleus.internal.NucLang;
import com.jcwhatever.nucleus.managed.language.Localizable;
import com.jcwhatever.nucleus.mixins.IPluginOwned;
import com.jcwhatever.nucleus.utils.CollectionUtils;
import com.jcwhatever.nucleus.utils.PreCon;
import com.jcwhatever.nucleus.utils.text.TextUtils;
import com.jcwhatever.nucleus.utils.text.components.IChatClickable.ClickAction;
import com.jcwhatever.nucleus.utils.text.components.IChatHoverable.HoverAction;
import com.jcwhatever.nucleus.utils.text.components.IChatMessage;
import com.jcwhatever.nucleus.utils.text.components.SimpleChatMessage;
import com.jcwhatever.nucleus.utils.text.format.args.ClickableArgModifier;
import com.jcwhatever.nucleus.utils.text.format.args.HoverableArgModifier;
import com.jcwhatever.nucleus.utils.text.format.args.TextArg;
import org.bukkit.command.CommandSender;
import org.bukkit.plugin.Plugin;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* Chat list paginator.
*/
public class ChatPaginator implements IPluginOwned {
@Localizable static final String _HEADER =
"{GRAY}------------------------------------\n" +
"{AQUA}{5: title}\n";
@Localizable static final String _FOOTER =
"\n{0: prev} {GRAY}Page {1: current page} of {2: total pages} {3: next}\n" +
"{GRAY}------------------------------------";
@Localizable static final String _SEARCH_HEADER = _HEADER +
"{ITALIC}{LIGHT_PURPLE}Search: '{4: search term}'\n";
@Localizable static final String _NO_ITEMS = "No items to display.";
@Localizable static final String _PAGE_NOT_FOUND = "Page {0: page} was not found.";
@Localizable static final String _PREV = "{AQUA}Prev";
@Localizable static final String _PREV_DISABLED = "{DARK_GRAY}Prev";
@Localizable static final String _PREV_HOVER = "{YELLOW}Click to go to the previous page.";
@Localizable static final String _NEXT = "{AQUA}Next";
@Localizable static final String _NEXT_DISABLED = "{DARK_GRAY}Next";
@Localizable static final String _NEXT_HOVER = "{YELLOW}Click to go to the next page.";
private final Plugin _plugin;
private final IMessenger _msg;
private final List<Object[]> _printList = new ArrayList<>(50);
protected int _itemsPerPage = 6;
protected String _headerFormat;
protected String _footerFormat;
protected String _searchHeaderFormat;
protected IChatMessage _title;
protected String _searchTerm;
protected IChatPaginatorCommands _commands;
protected TextArg _prev;
protected TextArg _next;
/**
* Constructor.
*
* <p>6 items per page, no title</p>
*
* @param plugin The owning plugin.
*/
public ChatPaginator(Plugin plugin) {
this(plugin, 6, null, null, "");
}
/**
* Constructor.
*
* <p>No title.</p>
*
* @param plugin The owning plugin.
* @param itemsPerPage Number of items to show per page.
*/
public ChatPaginator(Plugin plugin, int itemsPerPage) {
this(plugin, itemsPerPage, null, null, "");
}
/**
* Constructor.
*
* <p>6 items per page.</p>
*
* @param plugin The owning plugin.
* @param title The title to insert into the header.
* @param args Optional title format args.
*/
public ChatPaginator(Plugin plugin, CharSequence title, Object... args) {
this(plugin, 6, null, null, title, args);
}
/**
* Constructor.
*
* @param plugin The owning plugin.
* @param itemsPerPage Number of items to show per page.
* @param commands Command generator for clickable links.
* @param title The title to insert into the header.
* @param args Optional title format args.
*/
public ChatPaginator(Plugin plugin, int itemsPerPage,
@Nullable IChatPaginatorCommands commands,
CharSequence title, Object... args) {
PreCon.notNull(title);
PreCon.notNull(args);
_plugin = plugin;
_msg = Nucleus.getMessengerFactory().get(plugin);
_commands = commands;
_itemsPerPage = itemsPerPage;
_headerFormat = _HEADER;
_footerFormat = _FOOTER;
_searchHeaderFormat = _SEARCH_HEADER;
_title = TextUtils.format(title, args);
}
@Override
public Plugin getPlugin() {
return _plugin;
}
/**
* Get the paginator title.
*/
@Nullable
public String getTitle() {
return _title.toString();
}
/**
* Get the header text/format.
*
* @return The header or null if none.
*/
@Nullable
public String getHeader() {
return _headerFormat;
}
/**
* Set the header text/format.
*
* <p>Header format uses numbers in braces to insert title, current page and total pages:</p>
* <ul>
* <li>{0} = title</li>
* <li>{1} = current page</li>
* <li>{2} = total pages</li>
* </ul>
*
* @param header The header text.
*/
public void setHeader(@Nullable String header) {
_headerFormat = header;
}
/**
* Get the footer text/format.
*
* @return The footer or null if none.
*/
@Nullable
public String getFooter() {
return _footerFormat;
}
/**
* Set the footer text/format.
*
* <p>Footer format uses numbers in braces to insert title, current page and total pages:</p>
* <ul>
* <li>{0} = title</li>
* <li>{1} = current page</li>
* <li>{2} = total pages</li>
* </ul>
*
* @param footer The footer text.
*/
public void setFooter(@Nullable String footer) {
_footerFormat = footer;
}
/**
* Get the search header text/format.
*
* @return The search header or null if none.
*/
@Nullable
public String getSearchHeader() {
return _searchHeaderFormat;
}
/**
* Set the search header text/format.
*
* <p>Footer format uses numbers in braces to insert title, current page and total pages:</p>
* <ul>
* <li>{0} = title</li>
* <li>{1} = current page</li>
* <li>{2} = total pages</li>
* <li>{3} = search term</li>
* </ul>
*
* @param searchHeader The search header text.
*/
public void setSearchHeader(@Nullable String searchHeader) {
_searchHeaderFormat = searchHeader;
}
/**
* Get the search filter term.
*
* @return The search filter term or null if not set.
*/
@Nullable
public String getSearchTerm() {
return _searchTerm;
}
/**
* Set the search filter term.
*
* @param searchTerm The search text or null to remove search term.
*/
public void setSearchTerm(@Nullable String searchTerm) {
_searchTerm = searchTerm;
}
/**
* Add an item.
*
* <p>The format of the item is specified when the paginator is shown to a
* {@link org.bukkit.command.CommandSender}. The objects provided as
* parameters are inserted into the format.</p>
*
* @param args The object arguments inserted into the item format.
*/
public void add(Object...args) {
PreCon.notNull(args);
PreCon.greaterThanZero(args.length);
_printList.add(args);
}
/**
* Add an item with a format that overrides the item format used when
* displaying the paginator.
*
* @param format The format that applies to the item. See
* {@link com.jcwhatever.nucleus.utils.text.TextUtils.FormatTemplate}
* for ready-made formats.
* @param args The object arguments inserted into the item format.
*/
public void addFormatted(Object format, Object...args) {
PreCon.notNull(format);
PreCon.notNull(args);
_printList.add(new Object[]{new PreFormattedLine(format, args)});
}
/**
* Add all objects from a collection.
*
* <p>Each item in the collection is 1 item in the paginator. Multiple objects can
* be added per item by placing them in an {@link java.lang.Object[]}</p>
*
* @param collection The collection to add.
*/
public void addAll(Collection<?> collection) {
PreCon.notNull(collection);
for (Object object : collection) {
if (object instanceof Object[]) {
_printList.add((Object[]) object );
}
else {
_printList.add(new Object[] { object });
}
}
}
/**
* Get the total number of pages given the specified format.
*
* @param format The format.
*/
public int getTotalPages(Object format) {
int totalItems;
totalItems = _searchTerm == null ? _printList.size() : getSearchLines(format).size();
return (int) Math.ceil((double) totalItems / _itemsPerPage);
}
/**
* Show a page of the paginator to a {@link org.bukkit.command.CommandSender}.
*
* <p>The format argument only applies to items that are not added with a
* specific format.</p>
*
* @param sender The command sender to display the page to.
* @param page The page to display.
* @param format The format that applies to the items on the page. See
* {@link TextUtils.FormatTemplate}
* for ready-made formats.
*
*/
public void show(CommandSender sender, int page, Object format) {
PreCon.notNull(sender);
PreCon.notNull(format);
page = page > 0 ? page : 1;
int totalPages = getTotalPages(format);
loadCommands(page, totalPages);
IChatMessage header = getFormattedHeader(page, totalPages);
if (header.length() != 0)
_msg.tell(sender, header);
if (page < 1 || page > totalPages) {
if (page == 1) {
_msg.tell(sender, NucLang.get(getPlugin(), _NO_ITEMS));
}
else {
_msg.tell(sender, NucLang.get(getPlugin(), _PAGE_NOT_FOUND, page));
}
}
else if (_searchTerm == null) {
showAll(sender, page, format);
}
else {
showSearch(sender, page, format);
}
IChatMessage footer = getFormattedFooter(page, totalPages);
if (footer.length() != 0)
_msg.tell(sender, footer);
}
/**
* Show all lines from a page.
*/
protected void showAll(CommandSender sender, int page, Object format) {
int start = page * _itemsPerPage - _itemsPerPage;
int end = Math.min(start + _itemsPerPage - 1, _printList.size() - 1);
for (int i = start; i <= end; ++i) {
Object[] arguments = _printList.get(i);
Object localFormat = format;
if (arguments.length == 1 && arguments[0] instanceof PreFormattedLine) {
PreFormattedLine preFormatted = (PreFormattedLine) arguments[0];
localFormat = preFormatted.format;
arguments = preFormatted.arguments;
}
_msg.tell(sender, localFormat, arguments);
}
}
/**
* Show lines from search filtered results.
*/
protected void showSearch(CommandSender sender, int page, Object format) {
List<IChatMessage> lines = getSearchLines(format);
int start = page * _itemsPerPage - _itemsPerPage;
int end = Math.min(start + _itemsPerPage - 1, lines.size() - 1);
for (int i = start; i <= end; ++i) {
_msg.tell(sender, lines.get(i));
}
}
/*
* Get all formatted pagin lines filtered by the current search term.
*/
protected List<IChatMessage> getSearchLines(Object format) {
List<IChatMessage> lines = new ArrayList<>(_printList.size());
for (Object[] arguments : _printList) {
Object localFormat = format;
Object[] localArgs = arguments;
if (arguments.length == 1 && arguments[0] instanceof PreFormattedLine) {
PreFormattedLine preformatted = (PreFormattedLine) arguments[0];
localFormat = preformatted.format;
localArgs = preformatted.arguments;
}
lines.add(TextUtils.format(localFormat, localArgs));
}
return CollectionUtils.textSearch(lines, _searchTerm);
}
/**
* Get the header to use and format it.
*/
protected IChatMessage getFormattedHeader(int page, int totalPages) {
String format = _searchTerm != null ? _searchHeaderFormat : _headerFormat;
if (format == null || format.isEmpty())
return new SimpleChatMessage();
return NucLang.get(_plugin, format,
_prev, Math.max(1, page), Math.max(1, totalPages), _next, _searchTerm, _title);
}
/**
* Get the footer to use and format it.
*/
protected IChatMessage getFormattedFooter(int page, int totalPages) {
if (_footerFormat == null || _footerFormat.isEmpty())
return new SimpleChatMessage();
return NucLang.get(_plugin, _footerFormat,
_prev, Math.max(1, page), Math.max(1, totalPages), _next, _searchTerm, _title);
}
protected void loadCommands(int page, int totalPages) {
if (_commands == null) {
_prev = new TextArg("");
_next = _prev;
return;
}
String prevCommand = _commands.getPrevCommand(this, page, totalPages);
String nextCommand = _commands.getNextCommand(this, page, totalPages);
_prev = prevCommand == null
? new TextArg(NucLang.get(_plugin, _PREV_DISABLED))
: new TextArg(NucLang.get(_plugin, _PREV),
new ClickableArgModifier(ClickAction.RUN_COMMAND, prevCommand),
new HoverableArgModifier(HoverAction.SHOW_TEXT, NucLang.get(_plugin, _PREV_HOVER)));
_next = nextCommand == null
? new TextArg(NucLang.get(_plugin, _NEXT_DISABLED))
: new TextArg(NucLang.get(_plugin, _NEXT),
new ClickableArgModifier(ClickAction.RUN_COMMAND, nextCommand),
new HoverableArgModifier(HoverAction.SHOW_TEXT, NucLang.get(_plugin, _NEXT_HOVER)));
}
/**
* Used to store formatting information for a single item.
*/
protected static class PreFormattedLine {
public final Object format;
public final Object[] arguments;
public PreFormattedLine(Object format, Object[] arguments) {
this.format = format;
this.arguments = arguments;
}
}
}