src/main/java/mnm/mods/util/gui/GuiText.java
public class GuiText extends GuiComponent implements IGuiInput<String> {

    private final GuiTextField textField;
    private String hint;

    public GuiText() {
        this(new GuiTextField(0, Minecraft.getMinecraft().fontRenderer, 0, 0, 1, 1));
    }

    public GuiText(@Nonnull GuiTextField textField) {
        this.textField = textField;
        // This text field must not be calibrated for someone of your...
        // generous..ness
        // I'll add a few 0s to the maximum length...
        this.textField.setMaxStringLength(10000);

        // you look great, by the way.

    }

    @Subscribe
    public void textboxClick(GuiMouseEvent event) {
        if (event.getType() == MouseEvent.CLICK) {
            setFocused(true);

            int x = event.getMouseX();
            int y = event.getMouseY();

            // send to text field.
            textField.mouseClicked(x, y, 0);
        }
    }

    @Subscribe
    public void textboxType(GuiKeyboardEvent event) {
        if (Keyboard.isKeyDown(event.getKey())) {
            textField.textboxKeyTyped(event.getCharacter(), event.getKey());
        }
    }

    @Override
    public void setLocation(ILocation bounds) {
        updateTextbox(bounds);
        super.setLocation(bounds);
    }

    private void updateTextbox(ILocation loc) {
        int width = loc.getWidth();
        int height = loc.getHeight();
        // this interface is provided by liteloader. (Thanks, mum)
        IGuiTextField field = (IGuiTextField) this.textField;
        field.setInternalWidth(width);
        field.setHeight(height);
    }

    @Override
    public boolean isFocusable() {
        return true;
    }

    @Override
    public void setFocused(boolean focused) {
        super.setFocused(focused);
        textField.setFocused(focused);
    }

    @Override
    public void updateComponent() {
        super.updateComponent();
        textField.updateCursorCounter();
    }

    @Override
    public void drawComponent(int mouseX, int mouseY) {
        textField.drawTextBox();

        super.drawComponent(mouseX, mouseY);
        if (isFocused() && !StringUtils.isEmpty(getHint())) {
            // draw the hint above.
            drawCaption(getHint(), 1, -5);
        }
    }

    @Override
    public void setPrimaryColor(Color foreColor) {
        textField.setTextColor(foreColor.getHex());
        super.setPrimaryColor(foreColor);
    }

    @Override
    public String getValue() {
        return textField.getText();
    }

    @Override
    public void setValue(String value) {
        textField.setText(value);
        textField.setCursorPositionZero();
    }

    public String getHint() {
        return hint;
    }

    public void setHint(String hint) {
        this.hint = hint;
    }

    public GuiTextField getTextField() {
        return textField;
    }
}
src/main/java/mnm/mods/util/gui/GuiComponent.java
public abstract class GuiComponent extends Gui {

    private boolean enabled = true;
    private boolean visible = true;
    private boolean hovered;
    private boolean focused;
    private int touchValue;
    private int lastButton;
    private long lastButtonTime;

    protected Minecraft mc = Minecraft.getMinecraft();

    private Color secondaryColor;
    private Color primaryColor;
    private GuiPanel parent;
    private ILocation location = new Location();
    private Dimension minimumSize = new Dimension();
    private float scale = 1;
    private ITextComponent caption;

    private EventBus bus = new EventBus((exception, context) -> LogManager.getLogger().throwing(exception));

    public GuiComponent() {
        this.bus.register(this);
    }

    /**
     * Draws this component on screen.
     *
     * @param mouseX The mouse x
     * @param mouseY The mouse y
     */
    public void drawComponent(int mouseX, int mouseY) {
    }

    public void drawCaption(int x, int y) {
        getCaptionText()
                .map(ITextComponent::getFormattedText)
                .filter(((Predicate<String>) String::isEmpty).negate())
                .filter(t -> this.isHovered())
                .ifPresent(text -> this.drawCaption(text, x, y));
    }

    protected void drawCaption(String caption, int x, int y) {
        caption = StringEscapeUtils.unescapeJava(caption);
        String[] list = caption.split("\n\r?");

        int w = 0;
        // find the largest width
        for (String s : list) {
            w = Math.max(w, (int) (mc.fontRenderer.getStringWidth(s) * getActualScale()));
        }
        y -= mc.fontRenderer.FONT_HEIGHT * list.length;

        Point point = getActualLocation().getPoint();
        ScaledResolution sr = new ScaledResolution(mc);
        int w2 = w;
        int x2 = x;
        while (x2 - 8 + point.x + w2 + 20 > sr.getScaledWidth()) {
            x--;
            w2--;
        }
        // put it on top
        GlStateManager.pushMatrix();
        Gui.drawRect(x - 2, y - 2, x + w + 2, y + mc.fontRenderer.FONT_HEIGHT * list.length + 1,
                0xcc333333);
        drawBorders(x - 2, y - 2, x + w + 2, y + mc.fontRenderer.FONT_HEIGHT * list.length + 1,
                0xccaaaaaa);
        for (String s : list) {
            mc.fontRenderer.drawStringWithShadow(s, x, y, this.getPrimaryColorProperty().getHex());
            y += mc.fontRenderer.FONT_HEIGHT;
        }
        GlStateManager.popMatrix();
    }

    protected void drawBorders(int x1, int y1, int x2, int y2, int color) {
        this.drawVerticalLine(x1 - 1, y1 - 1, y2 + 1, color); // left
        this.drawHorizontalLine(x1 - 1, x2, y1 - 1, color); // top
        this.drawVerticalLine(x2, y1 - 1, y2 + 1, color); // right
        this.drawHorizontalLine(x1, x2 - 1, y2, color); // bottom
    }

    /**
     * Draws borders around the provided points. Uses a brightened background
     * color with 0xaa transparency.
     *
     * @param x1 left point
     * @param y1 upper point
     * @param x2 right point
     * @param y2 lower point
     */
    protected void drawBorders(int x1, int y1, int x2, int y2) {
        Color color = getSecondaryColorProperty();
        int r = color.getRed();
        int g = color.getGreen();
        int b = color.getBlue();
        double amt = .75;
        r += luminance(r, amt);
        g += luminance(g, amt);
        b += luminance(b, amt);
        color = Color.of(r, g, b, 0xaa);
        drawBorders(x1, y1, x2, y2, color.getHex());
    }

    private static int luminance(int o, double amt) {
        return (int) ((255 - o) * amt);
    }

    /**
     * Updates the component. Called when it is called on the {@link GuiScreen}.
     */
    public void updateComponent() {
    }

    /**
     * Handles the mouse input and sends it to the mouse and action listeners.
     */
    public void handleMouseInput() {
        if (!isEnabled()) {
            this.hovered = false;
            return;
        }
        if (mc.currentScreen != null) {
            float scale = getActualScale();
            Point point = scalePoint(new Point(Mouse.getX(), Mouse.getY()), mc.currentScreen);
            ILocation actual = getActualLocation();
            // adjust for position and scale
            int x = (int) ((point.x - actual.getXPos()) / scale);
            int y = (int) ((point.y - actual.getYPos()) / scale);

            int button = Mouse.getEventButton();
            int scroll = Mouse.getEventDWheel();

            this.hovered = point.x >= actual.getXPos() && point.x <= actual.getXWidth()
                    && point.y >= actual.getYPos() && point.y <= actual.getYHeight();

            getBus().post(new GuiMouseEvent(this, MouseEvent.RAW, x, y, button, scroll));

            if (Mouse.getEventButtonState()) {
                if (this.mc.gameSettings.touchscreen && this.touchValue++ > 0) {
                    return;
                }
                lastButton = button;
                lastButtonTime = Minecraft.getSystemTime();
                if (isHovered()) {
                    getBus().post(new GuiMouseEvent(this, MouseEvent.CLICK, x, y, button, 0));
                    getBus().post(new ActionPerformedEvent(this));
                }
            } else if (button != -1) {
                if (this.mc.gameSettings.touchscreen && --this.touchValue > 0) {
                    return;
                }
                lastButton = -1;
                if (isHovered())
                    getBus().post(new GuiMouseEvent(this, MouseEvent.RELEASE, x, y, button, 0));
            } else if (lastButton != -1 && lastButtonTime > 0) {
                long buttonTime = Minecraft.getSystemTime() - this.lastButtonTime;
                getBus().post(new GuiMouseEvent(this, MouseEvent.DRAG, x, y, this.lastButton, buttonTime));
            }

            if (scroll != 0) {
                getBus().post(new GuiMouseEvent(this, MouseEvent.SCROLL, x, y, -1, -1, scroll));
            }
        }
    }

    /**
     * Handles the keyboard input and sends it to the keyboard listeners.
     */
    public void handleKeyboardInput() {
        int key = Keyboard.getEventKey();
        char character = Keyboard.getEventCharacter();
        long time = Keyboard.getEventNanoseconds();
        GuiKeyboardEvent event = new GuiKeyboardEvent(this, key, character, time);
        getBus().post(event);
    }

    /**
     * Called when the screen is closed.
     */
    public void onClosed() {
        this.hovered = false;
    }

    /**
     * Gets the current location of this component.
     *
     * @return The current immutable location.
     */
    public ILocation getLocation() {
        return this.location;
    }

    /**
     * Sets the location of this component. In order to maintain encapsulation,
     * it is wrapped as an immutable.
     *
     * @param location The new location
     */
    public void setLocation(ILocation location) {
        this.location = location.asImmutable();
    }

    /**
     * Gets the event bus used for the gui events for this component. This
     * replaces the listener interfaces previously used. They should easily port
     * over by adding a {@link com.google.common.eventbus.Subscribe} annotation
     * to the method.
     *
     * @return The event bus
     */
    public EventBus getBus() {
        return bus;
    }

    /**
     * Gets the parent of this component. Will return {@code null} until it is
     * added to a panel by being used as the parameter to
     * {@link GuiPanel#addComponent(GuiComponent)} or
     * {@link GuiPanel#addComponent(GuiComponent, Object)}.
     *
     * @return The parent or null if there is none
     */
    public Optional<GuiPanel> getParent() {
        return Optional.ofNullable(this.parent);
    }


    /**
     * Sets the parent of this component. Should only be used by
     * {@link GuiPanel}.
     *
     * @param guiPanel The parent
     */
    void setParent(@Nullable GuiPanel guiPanel) {
        this.parent = guiPanel;
    }

    /**
     * Gets the top-most parent of this component. May return null if this
     * component has no parent and is not a {@link GuiPanel}.
     *
     * @return The root panel or {@code null}.
     */
    private Optional<GuiPanel> getRootPanel() {
        Optional<GuiPanel> panel = getParent();
        while (true) {
            Optional<GuiPanel> parent = panel.flatMap(GuiComponent::getParent);
            if (!parent.isPresent())
                break;
            panel = parent;
        }
        if (panel.isPresent())
            return panel;
        else if (this instanceof GuiPanel)
            return Optional.of((GuiPanel) this);
        else
            return Optional.empty();
    }

    public ILocation getActualLocation() {
        Location location = this.getLocation().copy();
        location.scale(getActualScale());
        getParent().map(GuiComponent::getActualLocation).ifPresent(loc1 ->
                location.move(loc1.getXPos(), loc1.getYPos())
        );
        return location;
    }

    public void setMinimumSize(Dimension size) {
        this.minimumSize = size;
    }

    public Dimension getMinimumSize() {
        return minimumSize;
    }

    /**
     * Sets the scale for this component.
     *
     * @param scale The scale
     */
    public void setScale(float scale) {
        this.scale = scale;
    }

    /**
     * Gets the scale for this component.
     *
     * @return The scale
     */
    public float getScale() {
        return scale;
    }

    /**
     * Gets the total scale of this component. Takes into account all the
     * parents.
     *
     * @return The scale
     */
    protected float getActualScale() {
        return getScale() * getParent().map(GuiComponent::getActualScale).orElse(1F);
    }

    public Optional<Color> getPrimaryColor() {
        return Optional.ofNullable(this.primaryColor);
    }

    public void setPrimaryColor(@Nullable Color color) {
        this.primaryColor = color;
    }

    public Optional<Color> getSecondaryColor() {
        return Optional.ofNullable(this.secondaryColor);
    }

    public void setSecondaryColor(@Nullable Color color) {
        this.secondaryColor = color;
    }

    public Color getPrimaryColorProperty() {
        return getProperty(GuiComponent::getPrimaryColor, Color.WHITE);
    }

    public Color getSecondaryColorProperty() {
        return getProperty(GuiComponent::getSecondaryColor, Color.of(0));
    }

    /**
     * Gets if this is enabled. Disabled components will not handle mouse or
     * keyboard events.
     *
     * @return True if enabled, false if disabled.
     */
    public boolean isEnabled() {
        return enabled;
    }

    /**
     * Sets if this is enabled or not. Disabled components will not handle mouse
     * or keyboard events.
     *
     * @param enabled True for enabled, false for disabled
     */
    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    /**
     * Gets if this component is visible. Non-visible components are not
     * rendered.
     *
     * @return The visibility state
     */
    public boolean isVisible() {
        return visible;
    }

    /**
     * Sets this component's visibility. Non-visible components are not
     * rendered.
     *
     * @param visible The visibility state
     */
    public void setVisible(boolean visible) {
        if (!visible) {
            this.onClosed();
        }
        this.visible = visible;
    }

    /**
     * Returns if the cursor is hovered over this component.
     *
     * @return THe hover state
     */
    public boolean isHovered() {
        return hovered && getParent().map(GuiComponent::isHovered).orElse(true);
    }

    public void setCaption(@Nullable ITextComponent text) {
        this.caption = text;
    }

    /**
     * Gets the caption which is shown when the mouse is hovering over this
     * component.
     *
     * @return The caption
     * @deprecated Use {@link #getCaptionText()} instead.
     */
    @Nullable
    @Deprecated
    public String getCaption() {
        return getCaptionText().map(ITextComponent::getFormattedText).orElse(null);
    }

    public Optional<ITextComponent> getCaptionText() {
        return Optional.ofNullable(caption);
    }

    /**
     * Gets the current focus of this component.
     *
     * @return The focus
     */
    public boolean isFocused() {
        return focused;
    }

    /**
     * Sets the component's focus
     *
     * @param focused The focus value
     */
    public void setFocused(boolean focused) {
        // check if is focusable
        if (isFocusable()) {
            if (focused) {
                // unfocus everything before focusing this one
                getRootPanel().ifPresent(GuiPanel::unfocusAll);
            }
            this.focused = focused;
        }
        // otherwise ignore
    }

    /**
     * Denotes if this is focusable. Override to change.
     *
     * @return Whether this is focusable
     */
    public boolean isFocusable() {
        return false;
    }

    private <T> T getProperty(final Function<GuiComponent, Optional<T>> prop, T def) {
        return getProperty(prop).orElse(def);
    }

    private <T> Optional<T> getProperty(final Function<GuiComponent, Optional<T>> prop) {
        Optional<T> result = prop.apply(this);
        Optional<GuiPanel> parent = getParent();
        if (!result.isPresent()) {
            result = parent.flatMap((GuiComponent p) -> p.getProperty(prop));
        }
        return result;
    }

    protected static Point scalePoint(Point point, GuiScreen screen) {
        Minecraft mc = Minecraft.getMinecraft();
        int x = point.x * screen.width / mc.displayWidth;
        int y = screen.height - point.y * screen.height / mc.displayHeight - 1;
        return new Point(x, y);
    }

    protected void drawModalCorners(TexturedModal modal) {
        ILocation location = getLocation();
        int x = 0;
        int y = 0;

        int w = location.getWidth() / 2;
        int w2 = location.getWidth() - w;
        int h = location.getHeight() / 2;
        int h2 = location.getHeight() - h + 1;

        int mx = modal.getXPos();
        int my = modal.getYPos();
        int mw = modal.getWidth() - w2;
        int mh = modal.getHeight() - h2;

        // bind the texture
        mc.getTextureManager().bindTexture(modal.getResourceLocation());

        // top left
        drawTexturedModalRect(x, y, mx, my, w, h);
        // top right
        drawTexturedModalRect(x + w, y, mx + mw, my, w2, h);
        // bottom left
        drawTexturedModalRect(x, y + h, mx, my + mh, w, h2);
        // bottom right
        drawTexturedModalRect(x + w, y + h, mx + mw, my + mh, w2, h2);

    }
}
src/main/java/mnm/mods/util/gui/VerticalLayout.java
public class VerticalLayout implements ILayout {

    private List<GuiComponent> list = Lists.newArrayList();

    @Override
    public void addComponent(GuiComponent comp, Object constraints) {
        if (constraints != null) {
            if (constraints instanceof Integer) {
                list.add((Integer) constraints, comp);
            } else {
                throw new IllegalArgumentException("Illegal constraint of type: "
                        + constraints.getClass().getName() + ". Only int accepted.");
            }
        } else {
            list.add(comp);
        }
    }

    @Override
    public void removeComponent(GuiComponent comp) {
        list.remove(comp);
    }

    @Override
    public void layoutComponents(GuiPanel parent) {
        int y = 0;
        for (GuiComponent comp : list) {
            Location loc = comp.getLocation().copy();
            loc.setXPos(0);
            loc.setYPos(y);
            comp.setLocation(loc);
            y += comp.getMinimumSize().height;
        }
    }

    @Override
    public Dimension getLayoutSize() {
        int width = 0;
        int height = 0;
        for (GuiComponent comp : list) {
            width = Math.max(width, comp.getLocation().getWidth());
            height += comp.getMinimumSize().height;
        }
        return new Dimension(width, height);
    }
}
src/main/java/mnm/mods/util/gui/config/SettingPanel.java
public abstract class SettingPanel<T extends SettingsFile> extends GuiPanel {

    private String displayString;

    /**
     * Called when this category is activated and displayed.
     */
    public void initGUI() {}

    /**
     * Sets the display string for this category.
     *
     * @param string The display string
     */
    public void setDisplayString(String string) {
        this.displayString = string;
    }

    /**
     * Gets the display string for this category. It is shown with the button
     * used to activate it.
     *
     * @return The display string
     */
    public String getDisplayString() {
        return this.displayString;
    }

    /**
     * Gets the {@link SettingsFile} used for this category. Used for loading
     * and saving the settings file.
     *
     * @return The settings
     */
    public abstract T getSettings();


}
src/main/java/mnm/mods/util/gui/GuiGridLayout.java
public class GuiGridLayout implements ILayout {

    private int cols;
    private int rows;

    private Map<ILocation, GuiComponent> grid = Maps.newHashMap();

    public GuiGridLayout(int columns, int rows) {
        this.cols = columns;
        this.rows = rows;
    }

    @Override
    public void addComponent(GuiComponent comp, Object constraints) {
        if (constraints == null) {
            throw new IllegalArgumentException("component requires constraints.");
        } else if (!(constraints instanceof int[])) {
            throw new IllegalArgumentException("Constraints must be an int array");
        }
        addComponent(comp, (int[]) constraints);
    }

    public void addComponent(GuiComponent comp, int[] constraints) {
        if (constraints.length != 2 && constraints.length != 4) {
            throw new IllegalArgumentException("Constraints must have either 2 or 4 elements.");
        }
        int x = constraints[0];
        int y = constraints[1];
        int w = 1;
        int h = 1;
        if (constraints.length == 4) {
            w = constraints[2];
            h = constraints[3];
        }
        ILocation rect = new Location(x, y, w, h);
        try {
            checkBoundsIfValid(rect);
            grid.put(rect.asImmutable(), comp);
        } catch (Exception e) {
            LogManager.getLogger().catching(e);
        }
    }

    /**
     * Checks if the rectangle is valid. A rectangle is valid if x and y are
     * above 0 and x + width < cols and y + height < rows
     */
    private void checkBoundsIfValid(ILocation b) {
        if (b.getXWidth() - 1 > cols || b.getYHeight() - 1 > rows || b.getXPos() < 0 || b.getYPos() < 0) {
            throw new IndexOutOfBoundsException(String.format(
                    "x:%s y:%s w:%s h:%s cols:%s rows:%s", b.getXPos(), b.getYPos(), b.getWidth(), b.getHeight(), cols, rows));
        }
        for (ILocation r : grid.keySet()) {
            if (b.contains(r)) {
                throw new IllegalArgumentException("Area " + b + " already contains "
                        + grid.get(r).getClass().getName());
            }
        }
    }

    @Override
    public void removeComponent(GuiComponent comp) {
        ILocation remove = null;
        for (Entry<ILocation, GuiComponent> entry : grid.entrySet()) {
            if (entry.getValue() == comp) {
                remove = entry.getKey();
                break;
            }
        }
        grid.remove(remove);
    }

    @Override
    public void layoutComponents(GuiPanel parent) {
        Dimension size = parent.getLocation().getSize();
        int colW = size.width / cols;
        int rowH = size.height / rows;
        for (Map.Entry<ILocation, GuiComponent> entry : grid.entrySet()) {
            ILocation bounds = entry.getKey();
            int x = bounds.getXPos() * colW;
            int width = bounds.getWidth() * colW;
            int y = bounds.getYPos() * rowH;
            int height = bounds.getHeight() * rowH;
            entry.getValue().setLocation(new Location(x, y, width, height));
        }
    }

    @Override
    public Dimension getLayoutSize() {
        return new Dimension(cols, rows);
    }

}
src/main/java/mnm/mods/tabbychat/util/Translation.java
public final class Translation {

    public static final String TABBYCHAT = "tabbychat";

    public static final String FORMAT_MESSAGE_WHISPER = "tabbychat.formats.messages.whisper";
    public static final String FORMAT_MESSAGE_ARROW = "tabbychat.formats.messages.arrow";
    public static final String FORMAT_MESSAGE_TO_FROM = "tabbychat.formats.messages.toFrom";
    public static final String FORMAT_MESSAGE_DISABLED = "tabbychat.formats.messages.disabled";

    public static final String DELIMS_ANGLES = "tabbychat.delims.angles";
    public static final String DELIMS_BRACES = "tabbychat.delims.braces";
    public static final String DELIMS_BRACKETS = "tabbychat.delims.brackets";
    public static final String DELIMS_PARENTHESIS = "tabbychat.delims.parenthesis";
    public static final String DELIMS_ANGLES_PARENS = "tabbychat.delims.anglesparens";
    public static final String DELIMS_ANGLES_BRAKETS = "tabbychat.delims.anglesbrackets";

    public static final String SETTINGS_TITLE = "tabbychat.settings.title";
    public static final String SETTINGS_SAVE = "tabbychat.settings.save";
    public static final String SETTINGS_CLOSE = "tabbychat.settings.close";

    // settings names
    public static final String SETTINGS_GENERAL = "tabbychat.settings.general";
    public static final String SETTINGS_SERVER = "tabbychat.settings.server";
    public static final String SETTINGS_FILTERS = "tabbychat.settings.filters";
    public static final String SETTINGS_COLORS = "tabbychat.settings.colors";
    public static final String SETTINGS_ADVANCED = "tabbychat.settings.advanced";

    // general settings
    public static final String LOG_CHAT = "tabbychat.settings.general.logChat";
    public static final String LOG_CHAT_DESC = "tabbychat.settings.general.logChat.desc";
    public static final String SPLIT_LOG = "tabbychat.settings.general.splitLog";
    public static final String SPLIT_LOG_DESC = "tabbychat.settings.general.splitLog.desc";
    public static final String TIMESTAMP = "tabbychat.settings.general.timestamp";
    public static final String TIMESTAMP_STYLE = "tabbychat.settings.general.timestamp.style";
    public static final String TIMESTAMP_COLOR = "tabbychat.settings.general.timestamp.color";
    public static final String ANTI_SPAM = "tabbychat.settings.general.antispam";
    public static final String ANTI_SPAM_DESC = "tabbychat.settings.general.antispam.desc";
    public static final String SPAM_PREJUDICE = "tabbychat.settings.general.antispam.prejudice";
    public static final String SPAM_PREJUDICE_DESC = "tabbychat.settings.general.antispam.prejudice.desc";
    public static final String UNREAD_FLASHING = "tabbychat.settings.general.unreadFlashing";
    public static final String CHECK_UPDATES = "tabbychat.settings.general.checkUpdates";

    // server settings
    public static final String CHANNELS_ENABLED = "tabbychat.settings.server.channelsEnabled";
    public static final String CHANNELS_ENABLED_DESC = "tabbychat.settings.server.channelsEnabled.desc";
    public static final String PM_ENABLED = "tabbychat.settings.server.pmEnabled";
    public static final String PM_ENABLED_DESC = "tabbychat.settings.server.pmEnabled.desc";
    public static final String CHANNEL_PATTERN = "tabbychat.settings.server.channelPattern";
    public static final String CHANNEL_PATTERN_DESC = "tabbychat.settings.server.channelPattern.desc";
    public static final String USE_DEFAULT = "tabbychat.settings.server.useDefault";
    public static final String MESSAGE_PATTERN = "tabbychat.settings.server.messagePattern";
    public static final String MESSAGE_PATTERN_DESC = "tabbychat.settings.server.messagePattern.desc";
    public static final String IGNORED_CHANNELS = "tabbychat.settings.server.ignoredChannels";
    public static final String IGNORED_CHANNELS_DESC = "tabbychat.settings.server.ignoredChannels.desc";
    public static final String DEFAULT_CHANNEL_COMMAND = "tabbychat.settings.server.channelCommand";
    public static final String DEFAULT_CHANNEL_COMMAND_DESC = "tabbychat.settings.server.channelCommand.desc";
    public static final String FILTERS = "tabbychat.settings.server.filters";
    public static final String FILTERS_NEW = "tabbychat.settings.server.filters.new";

    // filter settings
    public static final String FILTER_TITLE = "tabbychat.filter.title";
    public static final String FILTER_NAME = "tabbychat.filter.name";
    public static final String FILTER_DESTINATIONS = "tabbychat.filter.destinations";
    public static final String FILTER_DESTIONATIONS_DESC = "tabbychat.filter.destinations.desc";
    public static final String FILTER_HIDE = "tabbychat.filter.hideMatches";
    public static final String FILTER_AUDIO_NOTIFY = "tabbychat.filter.audioNotify";
    public static final String FILTER_EXPRESSION = "tabbychat.filter.expression";
    public static final String FILTER_REGEX = "tabbychat.filter.regex";
    public static final String FILTER_IGNORE_CASE = "tabbychat.filter.ignoreCase";
    public static final String FILTER_RAW_INPUT = "tabbychat.filter.rawInput";

    // channel settings
    public static final String CHANNEL_TITLE = "tabbychat.channel.title";
    public static final String CHANNEL_LABEL = "tabbychat.channel.label";
    public static final String CHANNEL_ALIAS = "tabbychat.channel.alias";
    public static final String CHANNEL_PREFIX = "tabbychat.channel.prefix";
    public static final String CHANNEL_HIDE_PREFIX = "tabbychat.channel.hidePrefix";
    public static final String CHANNEL_COMMAND = "tabbychat.channel.command";
    public static final String CHANNEL_NONE = "tabbychat.channel.none";
    public static final String CHANNEL_SELECT = "tabbychat.channel.select";
    public static final String CHANNEL_FORGET = "tabbychat.channel.forget";

    // color stuff
    public static final String COLOR_CHATBOX = "tabbychat.settings.colors.chatbox";
    public static final String COLOR_CHAT_BORDER = "tabbychat.settings.colors.chatborder";
    public static final String COLOR_CHAT_TEXT = "tabbychat.settings.colors.chattext";

    // advanced
    public static final String ADVANCED_FADE_TIME = "tabbychat.settings.advanced.fadetime";
    public static final String ADVANCED_CHAT_VISIBILITY = "tabbychat.settings.advanced.chatvisibility";
    public static final String ADVANCED_HIDE_DELIMS = "tabbychat.settings.advanced.hidedelims";
    public static final String ADVANCED_SPELLCHECK = "tabbychat.settings.advanced.spellcheck";
    public static final String EXPERIMENTAL = "tabbychat.settings.experimental";
    
    // messages
    public static final String WARN_COMPLETIONS = "tabbychat.warn.completions";

    private Translation() {}
}
src/main/java/mnm/mods/util/Color.java
public class Color {

    public static final Color BLACK = of(0xff000000);
    public static final Color DARK_BLUE = of(0xff0000aa);
    public static final Color DARK_GREEN = of(0xff00aa00);
    public static final Color DARK_AQUA = of(0xff00aaaa);
    public static final Color DARK_RED = of(0xffaa0000);
    public static final Color DARK_PURPLE = of(0xffaa00aa);
    public static final Color GOLD = of(0xffffaa00);
    public static final Color GRAY = of(0xffaaaaaa);
    public static final Color DARK_GRAY = of(0xff555555);
    public static final Color BLUE = of(0xff5555ff);
    public static final Color GREEN = of(0xff55ff55);
    public static final Color AQUA = of(0xff55ffff);
    public static final Color RED = of(0xffff5555);
    public static final Color LIGHT_PURPLE = of(0xffff55ff);
    public static final Color YELLOW = of(0xffffff55);
    public static final Color WHITE = of(0xffffffff);

    private static Random random = new Random();

    @SerializedName("r")
    private final int red;
    @SerializedName("g")
    private final int green;
    @SerializedName("b")
    private final int blue;
    @SerializedName("a")
    private final int alpha;

    /**
     * Creates a color using an html color code.
     *
     * @param hexColor The html color code
     */
    private Color(int hexColor) {
        this.alpha = hexColor >> 24 & 255;
        this.red = hexColor >> 16 & 255;
        this.green = hexColor >> 8 & 255;
        this.blue = hexColor & 255;
    }

    /**
     * Creates a color using RGBA color format.
     *
     * @param red Red color
     * @param green Green color
     * @param blue Blue color
     * @param alpha Transparency
     */
    private Color(int red, int green, int blue, int alpha) {
        this.red = red % 256;
        this.green = green % 256;
        this.blue = blue % 256;
        this.alpha = alpha % 256;
    }

    /**
     * Gets the html hex color code of this color usable by Minecraft's
     * {@link net.minecraft.client.gui.Gui}.
     *
     * @return The html hex code.
     */
    public int getHex() {
        int alphaI = alpha << 24;
        int redI = red << 16;
        int greenI = green << 8;
        return alphaI + redI + greenI + blue;
    }

    /**
     * Gets the red value.
     *
     * @return The red value
     */
    public int getRed() {
        return red;
    }

    /**
     * Gets the green value.
     *
     * @return The green value
     */
    public int getGreen() {
        return green;
    }

    /**
     * Gets the blue value.
     *
     * @return The blue value
     */
    public int getBlue() {
        return blue;
    }

    /**
     * Gets the alpha value.
     *
     * @return The alpha value
     */
    public int getAlpha() {
        return alpha;
    }

    /**
     * Convenience method for getting the html code for a RGBA color. Creates a
     * new object and calls {@link Color#getHex()}.
     *
     * @param red The red value
     * @param green The green value
     * @param blue The blue value
     * @param alpha The alpha value
     * @return The hexadecimal representation of this color.
     */
    public static int getColor(int red, int green, int blue, int alpha) {
        return of(red, green, blue, alpha).getHex();
    }

    /**
     * Generates a random color.
     *
     * @return A random color
     */
    public static Color random() {
        return of(random.nextInt());
    }

    public static Color of(int hexColor) {
        return new Color(hexColor);
    }

    public static Color of(int red, int green, int blue, int alpha) {
        return new Color(red, green, blue, alpha);
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + alpha;
        result = prime * result + blue;
        result = prime * result + green;
        result = prime * result + red;
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null || !(obj instanceof Color))
            return false;
        Color other = (Color) obj;
        return alpha == other.alpha
                && blue == other.blue
                && green == other.green
                && red == other.red;
    }
}
src/main/java/mnm/mods/tabbychat/api/Channel.java
public interface Channel {

    /**
     * Gets the name of this channel.
     *
     * @return The name
     */
    String getName();

    /**
     * Gets whether this channel is used for a personal message.
     *
     * @return PM status
     */
    boolean isPm();

    /**
     * Gets the alias that is displayed on the channel tab.
     *
     * @return The alias
     */
    String getAlias();

    /**
     * Sets the alias that is displayed on the channel tab.
     *
     * @param alias The new alias
     */
    void setAlias(String alias);

    /**
     * Gets the prefix that is inserted before the input.
     *
     * @return The prefix
     */
    String getPrefix();

    /**
     * Sets the prefix that is inserted before the input.
     *
     * @param prefix The new prefix
     */
    void setPrefix(String prefix);

    /**
     * Gets whether the prefix is hidden or not. A hidden prefix will not be
     * displayed while typing. Defaults to false.
     *
     * @return True if the prefix is hidden
     */
    boolean isPrefixHidden();

    /**
     * Sets whether the prefix is hidden or not. A hidden prefix will not be
     * displayed while typing.
     *
     * @param hidden Whether to hide the prefix
     */
    void setPrefixHidden(boolean hidden);

    String getCommand();

    void setCommand(String cmd);

    /**
     * Gets the channel's current status. A null indicates that there is no
     * status.
     *
     * @return The status or null
     */
    @Nullable
    ChannelStatus getStatus();

    /**
     * Sets the channel's current status.
     *
     * @param status The new status
     */
    void setStatus(@Nullable ChannelStatus status);

    /**
     * Opens the settings panel for this channel.
     */
    void openSettings();

    /**
     * Gets a list of {@link Message}s in this {@link Channel}.
     *
     * @return A list of messages
     */
    List<Message> getMessages();

    /**
     * Adds a message to the channel.
     *
     * @param text The message
     */
    void addMessage(ITextComponent text);

    /**
     * Adds a message to the channel with optional deletion. Any messages
     * previously added with the same id will be removed.
     *
     * @param text The message to send
     * @param id The id of the message
     */
    void addMessage(ITextComponent text, int id);

    /**
     * Removes messages at the given position.
     *
     * @param pos The position
     */
    void removeMessageAt(int pos);

    /**
     * Removes all messages with the given id.
     *
     * @param id The id
     */
    void removeMessages(int id);

    /**
     * Clears this channel of all message.
     */
    void clear();

}
src/main/java/mnm/mods/util/gui/GuiLabel.java
public class GuiLabel extends GuiComponent {

    private FancyFontRenderer fr;
    private ITextComponent text;
    private float angle;

    public GuiLabel() {
        this.fr = new FancyFontRenderer(Minecraft.getMinecraft().fontRenderer);
    }

    /**
     * Creates a label from a chat component.
     *
     * @param chat The text
     */
    public GuiLabel(ITextComponent chat) {
        this();
        this.setText(chat);
    }

    @Override
    public void drawComponent(int mouseX, int mouseY) {
        if (getText() == null)
            return;
        GlStateManager.pushMatrix();
        GlStateManager.rotate(angle, 0, 0, angle);
        if (angle < 180) {
            GlStateManager.translate(-angle / 1.5, -angle / 4, 0);
        } else {
            GlStateManager.translate(-angle / 15, angle / 40, 0);
        }

        fr.drawChat(getText(), 0, 1, getPrimaryColorProperty().getHex(), true);

        GlStateManager.popMatrix();
        super.drawComponent(mouseX, mouseY);
    }

    /**
     * Sets the string of this label
     *
     * @param text The string
     */
    public void setText(ITextComponent text) {
        this.text = text;
    }

    /**
     * Gets the string of this label
     *
     * @return The string
     */
    public ITextComponent getText() {
        return text;
    }

    public float getAngle() {
        return angle;
    }

    public void setAngle(float angle) {
        this.angle = angle;
    }

}
src/main/java/mnm/mods/util/gui/events/ActionPerformedEvent.java
public class ActionPerformedEvent extends GuiEvent {

    public ActionPerformedEvent(GuiComponent component) {
        super(component);
    }
}
src/main/java/mnm/mods/tabbychat/api/TabbyAPI.java
public abstract class TabbyAPI {

    private static TabbyAPI instance;

    protected TabbyAPI() {
        instance = this;
    }

    /**
     * Gets the API.
     *
     * @return The api
     */
    public static TabbyAPI getAPI() {
        return instance;
    }

    /**
     * Retrieve TabbyChat's version data. Version data contains the readable
     * version string and comparable double.
     *
     * @return The version data
     */
    public abstract VersionData getVersionData();

    /**
     * Gets the chat.
     *
     * @return The chat
     */
    public abstract Chat getChat();

    /**
     * Gets the common event bus used for chat messages. For gui related things,
     * use the Component's.
     *
     * @return The event bus
     */
    public abstract EventBus getBus();

}
src/main/java/mnm/mods/tabbychat/settings/ServerSettings.java
@ExposableOptions(strategy = ConfigStrategy.Unversioned)
public class ServerSettings extends SettingsFile {

    public GeneralServerSettings general = new GeneralServerSettings();
    public ValueList<UserFilter> filters = list();
    public ValueMap<ChatChannel> channels = map();
    public ValueMap<ChatChannel> pms = map();

    private transient final InetSocketAddress ip;

    public ServerSettings(InetSocketAddress url) {
        super(TabbyRef.MOD_ID + "/" + getIPForFileName(url), "server");
        this.ip = url;
    }

    private static String getIPForFileName(InetSocketAddress addr) {
        String ip;
        if (addr == null) {
            ip = "singleplayer";
        } else {
            String url = addr.getHostName();
            ip = "multiplayer/" + IPUtils.parse(url).getFileSafeAddress();
        }
        return ip;
    }

    public InetSocketAddress getIP() {
        return this.ip;
    }

}
src/main/java/mnm/mods/util/gui/BorderLayout.java
public class BorderLayout implements ILayout {

    private EnumMap<Position, GuiComponent> components = Maps.newEnumMap(Position.class);

    @Override
    public void addComponent(GuiComponent comp, Object constraints) {
        if (constraints == null || constraints instanceof Position) {
            addComponent((Position) constraints, comp);
        } else {
            throw new IllegalArgumentException(
                    "cannot add to layout: constraint must be a Position enum");
        }
    }

    private synchronized void addComponent(Position constraint, GuiComponent comp) {
        if (constraint == null) {
            constraint = Position.CENTER;
        }

        components.put(constraint, comp);
    }

    @Override
    public synchronized void removeComponent(GuiComponent comp) {
        components.entrySet().stream()
                .filter(c -> c.getValue().equals(comp))
                .findFirst()
                .ifPresent(c -> components.remove(c.getKey()));
    }

    @Override
    public void layoutComponents(GuiPanel parent) {
        ILocation pbounds = parent.getLocation();
        GuiComponent center = components.get(Position.CENTER);
        GuiComponent north = components.get(Position.NORTH);
        GuiComponent south = components.get(Position.SOUTH);
        GuiComponent west = components.get(Position.WEST);
        GuiComponent east = components.get(Position.EAST);

        if (north != null) {
            north.setLocation(new Location(0, 0, pbounds.getWidth(), north.getMinimumSize().height));
        }

        if (west != null) {
            Location bounds = new Location();
            bounds.setWidth(west.getMinimumSize().width);

            if (north != null) {
                bounds.setYPos(north.getLocation().getHeight());
            }

            if (south == null) {
                if (north == null) {
                    bounds.setHeight(pbounds.getHeight());
                } else {
                    bounds.setHeight(pbounds.getHeight() - north.getLocation().getHeight());
                }
            } else {
                if (north == null) {
                    bounds.setHeight(pbounds.getHeight() - south.getLocation().getHeight());
                } else {
                    bounds.setHeight(pbounds.getHeight() - south.getLocation().getHeight() - north.getLocation().getHeight());
                }
            }
            west.setLocation(bounds);
        }

        if (center != null) {
            Location bounds = new Location();

            if (north != null) {
                bounds.setYPos(north.getLocation().getHeight() + 1);
            }

            if (west != null) {
                bounds.setXPos(west.getLocation().getWidth());
            }

            if (east == null) {
                if (west == null) {
                    bounds.setWidth(pbounds.getWidth());
                } else {
                    bounds.setWidth(pbounds.getWidth() - west.getLocation().getWidth());
                }
            } else {
                if (west == null) {
                    bounds.setWidth(pbounds.getWidth() - east.getLocation().getWidth());
                } else {
                    bounds.setWidth(pbounds.getWidth() - east.getLocation().getWidth() - west.getLocation().getWidth());
                }
            }

            if (south == null) {
                if (north == null) {
                    bounds.setHeight(pbounds.getHeight());
                } else {
                    bounds.setHeight(pbounds.getHeight() - north.getLocation().getHeight());
                }
            } else {
                if (north == null) {
                    bounds.setHeight(pbounds.getHeight() - south.getLocation().getHeight() - 1);
                } else {
                    bounds.setHeight(pbounds.getHeight() - south.getLocation().getHeight() - north.getLocation().getHeight() - 2);
                }
            }
            center.setLocation(bounds);
        }

        if (east != null) {
            Location bounds = new Location();

            bounds.setXPos(pbounds.getWidth() - east.getMinimumSize().width);
            bounds.setWidth(east.getMinimumSize().width);
            if (north != null) {
                bounds.setYPos(north.getLocation().getHeight());
            }
            if (south == null) {
                if (north == null) {
                    bounds.setHeight(pbounds.getHeight());
                } else {
                    bounds.setHeight(pbounds.getHeight() - north.getMinimumSize().height);
                }
            } else {
                if (north == null) {
                    bounds.setHeight(pbounds.getHeight() - south.getMinimumSize().height);
                } else {
                    bounds.setHeight(pbounds.getHeight() - south.getMinimumSize().height - north.getMinimumSize().height);
                }
            }
            east.setLocation(bounds);
        }

        if (south != null) {

            int x = 0;
            int y = pbounds.getHeight() - south.getLocation().getHeight();
            int width = pbounds.getWidth();
            int height = south.getMinimumSize().height;

            south.setLocation(new Location(x, y, width, height));
        }
    }

    @Override
    public Dimension getLayoutSize() {
        int width = 0;
        int height = 0;
        for (Entry<Position, GuiComponent> comp : components.entrySet()) {
            Dimension size = comp.getValue().getMinimumSize();
            switch (comp.getKey()) {
            case CENTER:
                width += size.width;
                height += size.height;
                break;
            case EAST:
            case WEST:
                width += size.width;
                break;
            case NORTH:
            case SOUTH:
                height += size.height;
                break;
            }
        }
        return new Dimension(width, height);
    }

    public enum Position {
        NORTH,
        SOUTH,
        EAST,
        WEST,
        CENTER
    }
}
src/main/java/mnm/mods/util/gui/GuiPanel.java
public class GuiPanel extends GuiComponent implements Iterable<GuiComponent> {

    private List<GuiComponent> components = Lists.newArrayList();
    private GuiComponent overlay;
    private ILayout layout;

    public GuiPanel(ILayout layout) {
        setLayout(layout);
    }

    public GuiPanel() {
    }

    @Subscribe
    public void unfocus(GuiMouseEvent event) {
        // Unfocuses all focusable on click
        if (event.getType() == MouseEvent.CLICK) {
            unfocusAll();
        }
    }

    @Override
    public void drawComponent(int mouseX, int mouseY) {
        if (this.getOverlay().isPresent()) {
            getOverlay().get().drawComponent(mouseX, mouseY);
            return;
        }
        getLayout().ifPresent(layout -> layout.layoutComponents(this));
        this.components.stream()
                .filter(GuiComponent::isVisible)
                .forEach(gc -> {
                    GlStateManager.pushMatrix();
                    ILocation location = gc.getLocation();
                    int xPos = location.getXPos();
                    int yPos = location.getYPos();
                    int x = (int) (mouseX / gc.getScale()) - xPos;
                    int y = (int) (mouseY / gc.getScale()) - yPos;
                    GlStateManager.translate(xPos, yPos, 0F);
                    GlStateManager.scale(gc.getScale(), gc.getScale(), 1F);
                    gc.drawComponent(x, y);

                    GlStateManager.popMatrix();
                });

        super.drawComponent(mouseX, mouseY);
    }

    @Override
    public void drawCaption(int mouseX, int mouseY) {
        super.drawCaption(mouseX, mouseY);
        if (this.getOverlay().isPresent()) {
            getOverlay().get().drawCaption(mouseX, mouseY);
            return;
        }
        this.components.stream()
                .filter(GuiComponent::isVisible)
                .forEach(gc -> {
                    GlStateManager.pushMatrix();
                    ILocation location = gc.getLocation();
                    int xPos = location.getXPos();
                    int yPos = location.getYPos();
                    int x = (int) (mouseX / gc.getScale()) - xPos;
                    int y = (int) (mouseY / gc.getScale()) - yPos;
                    GlStateManager.translate(xPos, yPos, 0F);
                    GlStateManager.scale(gc.getScale(), gc.getScale(), 1F);

                    gc.drawCaption(x, y);

                    GlStateManager.popMatrix();
                });
    }

    @Override
    public void updateComponent() {
        getOverlayOrChildren().forEach(GuiComponent::updateComponent);

    }

    @Override
    public void handleMouseInput() {
        super.handleMouseInput();
        getOverlayOrChildren().forEach(GuiComponent::handleMouseInput);
    }

    @Override
    public void handleKeyboardInput() {
        super.handleKeyboardInput();
        getOverlayOrChildren().forEach(GuiComponent::handleKeyboardInput);
    }

    /**
     * Gets the number of components in this panel.
     *
     * @return The count
     */
    public int getComponentCount() {
        return this.components.size();
    }

    /**
     * Adds a component to this panel.
     *
     * @param guiComponent The component
     */
    public void addComponent(GuiComponent guiComponent) {
        addComponent(guiComponent, (Object) null);
    }

    /**
     * Adds a component to this panel with constraints.
     *
     * @param guiComponent The component
     * @param constraints  The constraints
     */
    public void addComponent(GuiComponent guiComponent, Object constraints) {
        if (guiComponent != null) {
                guiComponent.setParent(this);
                components.add(guiComponent);
                getLayout().ifPresent(layout -> layout.addComponent(guiComponent, constraints));
        }
    }

    /**
     * Removes all components from this panel.
     */
    public void clearComponents() {
            components.forEach(comp -> {
                comp.setParent(null);
                getLayout().ifPresent(layout -> layout.removeComponent(comp));
            });
            components.clear();
            setOverlay((GuiComponent) null);
    }

    /**
     * Removes a component from this panel.
     *
     * @param guiComp The component to remove
     */
    public void removeComponent(GuiComponent guiComp) {
            components.remove(guiComp);
            getLayout().ifPresent(layout -> layout.removeComponent(guiComp));
    }

    /**
     * Sets the layout for this panel.
     *
     * @param lmg The layout manager
     */
    public void setLayout(ILayout lmg) {
        this.layout = lmg;
    }

    /**
     * Gets the layout for this panel.
     *
     * @return The layout
     */
    public Optional<ILayout> getLayout() {
        return Optional.ofNullable(layout);
    }

    /**
     * Sets the overlay for this component. An overlay temporarily replaces the
     * contents of the panel with itself. The contents are placed back when the
     * overlay is set to null.
     *
     * @param gui The component to overlay
     */
    public void setOverlay(@Nullable GuiComponent gui) {
        if (gui != null) {
            gui.setParent(this);
            gui.setLocation(gui.getLocation().copy()
                    .setWidth(getLocation().getWidth())
                    .setHeight(getLocation().getHeight()));
        } else {
            this.unfocusAll();
        }
        this.overlay = gui;
    }

    public Optional<GuiComponent> getOverlay() {
        return Optional.ofNullable(overlay);
    }

    private List<GuiComponent> getOverlayOrChildren() {
        return getOverlay().map(Arrays::asList).orElse(ImmutableList.copyOf(this.components));
    }

    /**
     * Unfocuses all components in this panel that are focusable.
     */
    public void unfocusAll() {
        for (GuiComponent comp : components) {
            if (comp.isFocusable()) {
                comp.setFocused(false);
            } else if (comp instanceof GuiPanel) {
                ((GuiPanel) comp).unfocusAll();
            }
        }
    }

    @Override
    public void onClosed() {
        this.components.forEach(GuiComponent::onClosed);
        this.getOverlay().ifPresent(GuiComponent::onClosed);
    }

    @Nonnull
    @Override
    public Dimension getMinimumSize() {
        Dimension size = getLayout().map(ILayout::getLayoutSize).orElseGet(() -> {
            int width = 0;
            int height = 0;
            for (GuiComponent gc : components) {
                width = Math.max(width, gc.getLocation().getXPos() + gc.getLocation().getWidth());
                height = Math.max(height, gc.getLocation().getYPos() + gc.getLocation().getHeight());
            }
            return new Dimension(width, height);
        });

        return size;

    }

    @Deprecated
    @Override
    public Iterator<GuiComponent> iterator() {
        return components.iterator();
    }

}
src/main/java/mnm/mods/tabbychat/TabbyChat.java
public class TabbyChat extends TabbyAPI {

    private static final Logger LOGGER = LogManager.getLogger(TabbyRef.MOD_ID);

    private ChatManager chatManager;
    private GuiNewChatTC chatGui;
    private EventBus bus = new EventBus();
    private Spellcheck spellcheck;

    public TabbySettings settings;
    public ServerSettings serverSettings;

    private File dataFolder;
    private InetSocketAddress currentServer;

    private boolean updateChecked;

    public TabbyChat(File configPath) {
        super();
        this.dataFolder = new File(configPath, TabbyRef.MOD_ID);
    }

    public static TabbyChat getInstance() {
        return (TabbyChat) getAPI();
    }

    public static Logger getLogger() {
        return LOGGER;
    }

    @Override
    public VersionData getVersionData() {
        return new Version();
    }

    @Override
    public ChatManager getChat() {
        return chatManager;
    }

    public GuiNewChatTC getChatGui() {
        return chatGui;
    }

    @Override
    public EventBus getBus() {
        return bus;
    }

    public Spellcheck getSpellcheck() {
        return spellcheck;
    }

    void openSettings(SettingPanel<?> setting) {
        GuiSettingsScreen screen = new GuiSettingsScreen(setting);
        Minecraft.getMinecraft().displayGuiScreen(screen);
    }

    public InetSocketAddress getCurrentServer() {
        return this.currentServer;
    }

    public File getDataFolder() {
        return dataFolder;
    }

    public void init() {

        // Set global settings
        settings = new TabbySettings();
        LiteLoader.getInstance().registerExposable(settings, null);

        spellcheck = new Spellcheck(getDataFolder());

        // Keeps the current language updated whenever it is changed.
        IReloadableResourceManager irrm = (IReloadableResourceManager) Minecraft.getMinecraft().getResourceManager();
        irrm.registerReloadListener(spellcheck);

        bus.register(new ChatAddonAntiSpam());
        bus.register(new FilterAddon());
        bus.register(new ChatLogging(new File("logs/chat")));

    }

    public void postInit(MnmUtils utils) {
        // gui related stuff should be done here
        chatManager = new ChatManager(this);
        // this is set here because status relies on `chatManager`.
        ChatChannel.DEFAULT_CHANNEL.setStatus(ChannelStatus.ACTIVE);
        chatGui = new GuiNewChatTC(Minecraft.getMinecraft(), chatManager);

        utils.setChatProxy(new TabbedChatProxy());
    }

    public void onJoin(@Nullable SocketAddress address) {
        if (address instanceof InetSocketAddress) {
            this.currentServer = (InetSocketAddress) address;
        } else {
            this.currentServer = null;
        }

        // Set server settings
        serverSettings = new ServerSettings(currentServer);
        LiteLoader.getInstance().registerExposable(serverSettings, null);

        try {
            hookIntoChat(Minecraft.getMinecraft().ingameGUI);
        } catch (Exception e) {
            LOGGER.fatal("Unable to hook into chat.  This is bad.", e);
        }
        // load chat
        File conf = serverSettings.getFile().getParentFile();
        try {
            chatManager.loadFrom(conf);
        } catch (Exception e) {
            LOGGER.warn("Unable to load chat data.", e);
        }

        if (this.settings.general.checkUpdates.get() && !updateChecked) {
            UpdateChecker.runUpdateCheck(TabbedChatProxy.INSTANCE, TabbyRef.getVersionData());
            updateChecked = true;
        }
    }

    @SuppressWarnings("MixinClassReference")
    private void hookIntoChat(GuiIngame guiIngame) throws Exception {
        if (!GuiNewChatTC.class.isAssignableFrom(guiIngame.getChatGUI().getClass())) {
            ((IGuiIngame) guiIngame).setPersistantChatGUI(chatGui);
            LOGGER.info("Successfully hooked into chat.");
        }
    }

    private class Version implements VersionData {
        @Override
        public double getRevision() {
            return TabbyRef.MOD_REVISION;
        }

        @Override
        public String getVersion() {
            return TabbyRef.MOD_VERSION;
        }
    }
}
src/main/java/mnm/mods/util/gui/GuiButton.java
public class GuiButton extends GuiComponent {

    private static final ResourceLocation WIDGETS = new ResourceLocation("textures/gui/widgets.png");
    private static final TexturedModal MODAL_NORMAL = new TexturedModal(WIDGETS, 0, 66, 200, 20);
    private static final TexturedModal MODAL_HOVER = new TexturedModal(WIDGETS, 0, 86, 200, 20);
    private static final TexturedModal MODAL_DISABLE = new TexturedModal(WIDGETS, 0, 46, 200, 20);

    private String text = "";
    private SoundEvent sound;

    /**
     * Instantiates a new button with {@code text} as the display string.
     *
     * @param text The display string
     */
    public GuiButton(String text) {
        this.setText(text);
        setSound(SoundEvents.UI_BUTTON_CLICK);
    }

    @Subscribe
    public void onClick(ActionPerformedEvent action) {
        SoundEvent sound = getSound();
        if (sound != null) {
            mc.getSoundHandler().playSound(PositionedSoundRecord.getMasterRecord(sound, 1.0F));
        }
    }

    /**
     * Sets the display text for this button.
     *
     * @param text The new text
     */
    public void setText(String text) {
        if (text == null) {
            text = "";
        }
        this.text = text;
    }

    /**
     * Gets the display text for this button.
     *
     * @return The text
     */
    public String getText() {
        return this.text;
    }

    public void setSound(SoundEvent sound) {
        this.sound = sound;
    }

    public SoundEvent getSound() {
        return sound;
    }

    @Override
    public void drawComponent(int mouseX, int mouseY) {
        FontRenderer fontrenderer = mc.fontRenderer;
        ILocation bounds = getLocation();

        mc.getTextureManager().bindTexture(WIDGETS);
        GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);

        TexturedModal modal = this.getHoverState(isHovered());
        GlStateManager.enableBlend();
        OpenGlHelper.glBlendFunc(770, 771, 1, 0);
        GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);

        this.drawModalCorners(modal);

        int textColor = 0xE0E0E0;

        if (!this.isEnabled()) {
            textColor = 0xA0A0A0;
        } else if (this.isHovered()) {
            textColor = 0xFFFFA0;
        }

        this.drawCenteredString(fontrenderer, getText(), bounds.getWidth() / 2, (bounds.getHeight() - 8) / 2,
                textColor);

        super.drawComponent(mouseX, mouseY);
    }

    private TexturedModal getHoverState(boolean hovered) {
        TexturedModal modal = GuiButton.MODAL_NORMAL;

        if (!this.isEnabled()) {
            modal = GuiButton.MODAL_DISABLE;
        } else if (hovered) {
            modal = GuiButton.MODAL_HOVER;
        }

        return modal;
    }

    @Nonnull
    @Override
    public Dimension getMinimumSize() {
        return new Dimension(mc.fontRenderer.getStringWidth(this.getText()) + 8, 20);
    }

}
src/main/java/mnm/mods/util/gui/GuiScrollingPanel.java
public class GuiScrollingPanel extends GuiPanel {

    private GuiPanel panel;

    public GuiScrollingPanel() {
        super(new BorderLayout());
        this.panel = new GuiPanel();
        this.panel.setLocation(new Location(0, 0, 100000, 100000));
        GuiPanel panel = new GuiPanel();
        panel.addComponent(this.panel);
        this.addComponent(panel, Position.CENTER);
        this.addComponent(new Scrollbar(), Position.EAST);
    }

    @Override
    public void drawComponent(int mouseX, int mouseY) {
        ILocation actual = getActualLocation();
        ILocation rect = getLocation();

        glEnable(GL_SCISSOR_TEST);
        glScissor(actual.getXPos() * 2, mc.displayHeight - rect.getHeight() * 2 - actual.getYPos() * 2, rect.getWidth() * 2, rect.getHeight() * 2);

        super.drawComponent(mouseX, mouseY);

        glDisable(GL_SCISSOR_TEST);
    }

    @Subscribe
    public void scroll(GuiMouseEvent event) {
        if (event.getType() == MouseEvent.SCROLL) {
            Location rect = panel.getLocation().copy();
            int scr = rect.getYPos() + event.getScroll() / 12;
            rect.setYPos(scr);

            panel.getParent().map(GuiComponent::getLocation).ifPresent(prect -> {
                Dimension dim = panel.getMinimumSize();
                if (rect.getYPos() + dim.height < prect.getHeight()) {
                    rect.setYPos(prect.getHeight() - dim.height);
                }
            });
            if (rect.getYPos() > 0)
                rect.setYPos(0);

            panel.setLocation(rect);
        }
    }

    public GuiPanel getContentPanel() {
        return panel;
    }

    @Nonnull
    @Override
    public Dimension getMinimumSize() {
        return getLocation().getSize();
    }

    // TODO Make draggable
    private class Scrollbar extends GuiComponent {

        @Override
        public void drawComponent(int mouseX, int mouseY) {
            int scroll = panel.getLocation().getYPos();
            int max = GuiScrollingPanel.this.getLocation().getHeight();
            int total = panel.getMinimumSize().height;
            if (total <= max) {
                return;
            }
            total -= max;
            Gui.drawRect(0, 20, 10, 10, -1);
            int size = Math.max(max / 2, 10);
            float perc = ((float) scroll / (float) total) * ((float) size / (float) max);
            int pos = (int) (-perc * max);

            Gui.drawRect(-1, pos, 0, pos + size - 1, -1);
            super.drawComponent(mouseX, mouseY);
        }
    }
}


Question: package mnm.mods.tabbychat.gui.settings;
import static mnm.mods.tabbychat.util.Translation.*;
import com.google.common.eventbus.Subscribe;
import mnm.mods.tabbychat.TabbyChat;
import mnm.mods.tabbychat.api.Channel;
import mnm.mods.tabbychat.api.TabbyAPI;
import mnm.mods.tabbychat.settings.ServerSettings;
import mnm.mods.util.Color;
import mnm.mods.util.Location;
import mnm.mods.util.gui.BorderLayout;
import mnm.mods.util.gui.GuiButton;
import mnm.mods.util.gui.GuiCheckbox;
import mnm.mods.util.gui.GuiComponent;
import mnm.mods.util.gui.GuiGridLayout;
import mnm.mods.util.gui.GuiLabel;
import mnm.mods.util.gui.GuiPanel;
import mnm.mods.util.gui.GuiScrollingPanel;
import mnm.mods.util.gui.GuiText;
import mnm.mods.util.gui.VerticalLayout;
import mnm.mods.util.gui.config.SettingPanel;
import mnm.mods.util.gui.events.ActionPerformedEvent;
import net.minecraft.client.resources.I18n;
import net.minecraft.util.text.TextComponentTranslation;



public class GuiSettingsChannel extends SettingPanel<ServerSettings> {

    private Channel channel;

    private GuiScrollingPanel channels;
    private GuiPanel panel;

Answer:     private GuiText alias;