package org.mafagafogigante.dungeon.gui; import org.mafagafogigante.dungeon.game.ColoredString; import org.mafagafogigante.dungeon.game.Writable; import org.mafagafogigante.dungeon.logging.DungeonLogger; import javax.swing.JTextPane; import javax.swing.text.BadLocationException; import javax.swing.text.DefaultStyledDocument; import javax.swing.text.MutableAttributeSet; import javax.swing.text.SimpleAttributeSet; import javax.swing.text.StyleConstants; import javax.swing.text.StyledDocument; /** * Controls writing to a JTextPane in order to maximize performance by avoiding unnecessary renderings. * * <p>This is done by holding two different StyledDocuments, one of which is updated while the other is exhibited, and * swapping them when the update is completed. * * <p>If an object of this class is being used to update a JTextPane, the document currently assigned to that JTextPane * should never be modified directly, but always through the write method of the SwappingStyledDocument object. */ final class SwappingStyledDocument { private final JTextPane textPane; private StyledDocument activeDocument = new DefaultStyledDocument(); private StyledDocument inactiveDocument = new DefaultStyledDocument(); /** * Constructs a new SwappingStyleDocument for the provided JTextPane. */ public SwappingStyledDocument(JTextPane textPane) { this.textPane = textPane; textPane.setDocument(activeDocument); } void write(Writable writable, WritingSpecifications specifications) { updateInactiveDocument(writable); swapDocuments(specifications); updateInactiveDocument(writable); } private void updateInactiveDocument(Writable writable) { writeToDocument(inactiveDocument, writable); } private void writeToDocument(StyledDocument inactiveDocument, Writable writable) { for (ColoredString coloredString : writable.toColoredStringList()) { MutableAttributeSet attributeSet = new SimpleAttributeSet(); StyleConstants.setForeground(attributeSet, coloredString.getColor()); try { inactiveDocument.insertString(inactiveDocument.getLength(), coloredString.getString(), attributeSet); } catch (BadLocationException warn) { DungeonLogger.warning("insertString resulted in a BadLocationException."); } } } private void swapDocuments(WritingSpecifications specifications) { final StyledDocument oldActiveDocument = activeDocument; activeDocument = inactiveDocument; inactiveDocument = oldActiveDocument; textPane.setDocument(activeDocument); textPane.setCaretPosition(specifications.shouldScrollDown() ? activeDocument.getLength() : 0); } /** * Clears this document. */ void clear() { // It doesn't matter which document we erase first. This should only cause one rendering. clear(activeDocument); clear(inactiveDocument); } private void clear(StyledDocument document) { try { document.remove(0, document.getLength()); } catch (BadLocationException ignored) { // Never happens. } } }