Newer
Older
ultramine_bukkit / src / main / java / org / bukkit / util / ChatPaginator.java
@vlad20012 vlad20012 on 24 Feb 2017 5 KB initial
package org.bukkit.util;

import org.bukkit.ChatColor;

import java.util.LinkedList;
import java.util.List;

/**
 * The ChatPaginator takes a raw string of arbitrary length and breaks it down
 * into an array of strings appropriate for displaying on the Minecraft player
 * console.
 */
public class ChatPaginator
{
	public static final int GUARANTEED_NO_WRAP_CHAT_PAGE_WIDTH = 55; // Will never wrap, even with the largest characters
	public static final int AVERAGE_CHAT_PAGE_WIDTH = 65; // Will typically not wrap using an average character distribution
	public static final int UNBOUNDED_PAGE_WIDTH = Integer.MAX_VALUE;
	public static final int OPEN_CHAT_PAGE_HEIGHT = 20; // The height of an expanded chat window
	public static final int CLOSED_CHAT_PAGE_HEIGHT = 10; // The height of the default chat window
	public static final int UNBOUNDED_PAGE_HEIGHT = Integer.MAX_VALUE;

	/**
	 * Breaks a raw string up into pages using the default width and height.
	 *
	 * @param unpaginatedString The raw string to break.
	 * @param pageNumber        The page number to fetch.
	 * @return A single chat page.
	 */
	public static ChatPage paginate(String unpaginatedString, int pageNumber)
	{
		return paginate(unpaginatedString, pageNumber, GUARANTEED_NO_WRAP_CHAT_PAGE_WIDTH, CLOSED_CHAT_PAGE_HEIGHT);
	}

	/**
	 * Breaks a raw string up into pages using a provided width and height.
	 *
	 * @param unpaginatedString The raw string to break.
	 * @param pageNumber        The page number to fetch.
	 * @param lineLength        The desired width of a chat line.
	 * @param pageHeight        The desired number of lines in a page.
	 * @return A single chat page.
	 */
	public static ChatPage paginate(String unpaginatedString, int pageNumber, int lineLength, int pageHeight)
	{
		String[] lines = wordWrap(unpaginatedString, lineLength);

		int totalPages = lines.length / pageHeight + (lines.length % pageHeight == 0 ? 0 : 1);
		int actualPageNumber = pageNumber <= totalPages ? pageNumber : totalPages;

		int from = (actualPageNumber - 1) * pageHeight;
		int to = from + pageHeight <= lines.length ? from + pageHeight : lines.length;
		String[] selectedLines = Java15Compat.Arrays_copyOfRange(lines, from, to);

		return new ChatPage(selectedLines, actualPageNumber, totalPages);
	}

	/**
	 * Breaks a raw string up into a series of lines. Words are wrapped using
	 * spaces as decimeters and the newline character is respected.
	 *
	 * @param rawString  The raw string to break.
	 * @param lineLength The length of a line of text.
	 * @return An array of word-wrapped lines.
	 */
	public static String[] wordWrap(String rawString, int lineLength)
	{
		// A null string is a single line
		if(rawString == null)
		{
			return new String[]{""};
		}

		// A string shorter than the lineWidth is a single line
		if(rawString.length() <= lineLength && !rawString.contains("\n"))
		{
			return new String[]{rawString};
		}

		char[] rawChars = (rawString + ' ').toCharArray(); // add a trailing space to trigger pagination
		StringBuilder word = new StringBuilder();
		StringBuilder line = new StringBuilder();
		List<String> lines = new LinkedList<String>();
		int lineColorChars = 0;

		for(int i = 0; i < rawChars.length; i++)
		{
			char c = rawChars[i];

			// skip chat color modifiers
			if(c == ChatColor.COLOR_CHAR)
			{
				word.append(ChatColor.getByChar(rawChars[i + 1]));
				lineColorChars += 2;
				i++; // Eat the next character as we have already processed it
				continue;
			}

			if(c == ' ' || c == '\n')
			{
				if(line.length() == 0 && word.length() > lineLength)
				{ // special case: extremely long word begins a line
					for(String partialWord : word.toString().split("(?<=\\G.{" + lineLength + "})"))
					{
						lines.add(partialWord);
					}
				}
				else if(line.length() + word.length() - lineColorChars == lineLength)
				{ // Line exactly the correct length...newline
					line.append(word);
					lines.add(line.toString());
					line = new StringBuilder();
					lineColorChars = 0;
				}
				else if(line.length() + 1 + word.length() - lineColorChars > lineLength)
				{ // Line too long...break the line
					for(String partialWord : word.toString().split("(?<=\\G.{" + lineLength + "})"))
					{
						lines.add(line.toString());
						line = new StringBuilder(partialWord);
					}
					lineColorChars = 0;
				}
				else
				{
					if(line.length() > 0)
					{
						line.append(' ');
					}
					line.append(word);
				}
				word = new StringBuilder();

				if(c == '\n')
				{ // Newline forces the line to flush
					lines.add(line.toString());
					line = new StringBuilder();
				}
			}
			else
			{
				word.append(c);
			}
		}

		if(line.length() > 0)
		{ // Only add the last line if there is anything to add
			lines.add(line.toString());
		}

		// Iterate over the wrapped lines, applying the last color from one line to the beginning of the next
		if(lines.get(0).length() == 0 || lines.get(0).charAt(0) != ChatColor.COLOR_CHAR)
		{
			lines.set(0, ChatColor.WHITE + lines.get(0));
		}
		for(int i = 1; i < lines.size(); i++)
		{
			final String pLine = lines.get(i - 1);
			final String subLine = lines.get(i);

			char color = pLine.charAt(pLine.lastIndexOf(ChatColor.COLOR_CHAR) + 1);
			if(subLine.length() == 0 || subLine.charAt(0) != ChatColor.COLOR_CHAR)
			{
				lines.set(i, ChatColor.getByChar(color) + subLine);
			}
		}

		return lines.toArray(new String[lines.size()]);
	}

	public static class ChatPage
	{

		private String[] lines;
		private int pageNumber;
		private int totalPages;

		public ChatPage(String[] lines, int pageNumber, int totalPages)
		{
			this.lines = lines;
			this.pageNumber = pageNumber;
			this.totalPages = totalPages;
		}

		public int getPageNumber()
		{
			return pageNumber;
		}

		public int getTotalPages()
		{
			return totalPages;
		}

		public String[] getLines()
		{

			return lines;
		}
	}
}