// Near Infinity - An Infinity Engine Browser and Editor // Copyright (C) 2001 - 2005 Jon Olav Hauglid // See LICENSE.txt for license information package org.infinity.datatype; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.ActionListener; import java.io.IOException; import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.util.EnumMap; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JPanel; import javax.swing.text.AttributeSet; import javax.swing.text.BadLocationException; import org.fife.ui.rsyntaxtextarea.RSyntaxDocument; import org.fife.ui.rtextarea.RTextArea; import org.infinity.gui.InfinityScrollPane; import org.infinity.gui.InfinityTextArea; import org.infinity.gui.StructViewer; import org.infinity.gui.ViewerUtil; import org.infinity.icon.Icons; import org.infinity.resource.AbstractStruct; import org.infinity.resource.StructEntry; import org.infinity.util.Misc; import org.infinity.util.io.StreamUtils; public final class TextEdit extends Datatype implements Editable, IsTextual { public static enum EOLType { UNIX, WINDOWS } public static enum Align { LEFT, RIGHT, TOP, BOTTOM } private static final EnumMap<EOLType, String> EOL = new EnumMap<EOLType, String>(EOLType.class); static { EOL.put(EOLType.UNIX, "\n"); EOL.put(EOLType.WINDOWS, "\r\n"); } private InfinityTextArea textArea; private Align buttonAlign; private ByteBuffer buffer; private String text; private EOLType eolType; private Charset charset; private boolean terminateString, editable; public TextEdit(ByteBuffer buffer, int offset, int length, String name) { this(null, buffer, offset, length, name, Align.RIGHT); } public TextEdit(ByteBuffer buffer, int offset, int length, String name, Align buttonAlignment) { this(null, buffer, offset, length, name, buttonAlignment); } public TextEdit(StructEntry parent, ByteBuffer buffer, int offset, int length, String name) { this(parent, buffer, offset, length, name, Align.RIGHT); } public TextEdit(StructEntry parent, ByteBuffer buffer, int offset, int length, String name, Align buttonAlignment) { super(parent, offset, length, name); this.buffer = StreamUtils.getByteBuffer(getSize()); read(buffer, offset); this.eolType = EOLType.UNIX; this.charset = Charset.defaultCharset(); this.terminateString = false; this.editable = true; this.buttonAlign = (buttonAlignment != null) ? buttonAlignment : Align.RIGHT; } // --------------------- Begin Interface Editable --------------------- @Override public JComponent edit(ActionListener container) { JButton bUpdate; if (textArea == null) { textArea = new InfinityTextArea(1, 200, true); textArea.setHighlightCurrentLine(editable); textArea.setWrapStyleWord(true); textArea.setLineWrap(true); textArea.setMargin(new Insets(3, 3, 3, 3)); textArea.setDocument(new FixedDocument(textArea, buffer.limit())); textArea.setEditable(editable); } textArea.setText(toString()); textArea.setCaretPosition(0); textArea.discardAllEdits(); InfinityScrollPane scroll = new InfinityScrollPane(textArea, true); scroll.setLineNumbersEnabled(false); bUpdate = new JButton("Update value", Icons.getIcon(Icons.ICON_REFRESH_16)); bUpdate.setEnabled(editable); bUpdate.addActionListener(container); bUpdate.setActionCommand(StructViewer.UPDATE_VALUE); GridBagLayout gbl = new GridBagLayout(); GridBagConstraints gbc = new GridBagConstraints(); JPanel panel = new JPanel(gbl); int curGridX = 0; int curGridY = 0; if (buttonAlign == Align.TOP) { gbc = ViewerUtil.setGBC(gbc, curGridX, curGridY, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(4, 0, 6, 0), 0, 0); panel.add(bUpdate, gbc); curGridY++; } if (buttonAlign == Align.LEFT) { gbc = ViewerUtil.setGBC(gbc, curGridX, curGridY, 1, 1, 0.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0, 0, 0, 6), 0, 0); panel.add(bUpdate, gbc); curGridX++; } gbc = ViewerUtil.setGBC(gbc, curGridX, curGridY, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0); panel.add(scroll, gbc); if (buttonAlign == Align.RIGHT) { gbc = ViewerUtil.setGBC(gbc, curGridX + 1, curGridY, 1, 1, 0.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0, 6, 0, 0), 0, 0); panel.add(bUpdate, gbc); } if (buttonAlign == Align.BOTTOM) { gbc = ViewerUtil.setGBC(gbc, curGridX, curGridY + 1, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(6, 0, 4, 0), 0, 0); panel.add(bUpdate, gbc); } panel.setMinimumSize(DIM_BROAD); panel.setPreferredSize(DIM_BROAD); return panel; } @Override public void select() { } @Override public boolean updateValue(AbstractStruct struct) { text = textArea.getText(); // notifying listeners fireValueUpdated(new UpdateEvent(this, struct)); return true; } // --------------------- End Interface Editable --------------------- // --------------------- Begin Interface Writeable --------------------- @Override public void write(OutputStream os) throws IOException { StreamUtils.writeBytes(os, toBuffer()); } // --------------------- End Interface Writeable --------------------- //--------------------- Begin Interface Readable --------------------- @Override public int read(ByteBuffer buffer, int offset) { StreamUtils.copyBytes(buffer, offset, this.buffer, 0, getSize()); return offset + getSize(); } //--------------------- End Interface Readable --------------------- //--------------------- Begin Interface IsTextual --------------------- @Override public String getText() { if (text == null) { buffer.position(0); String s = StreamUtils.readString(buffer, buffer.limit(), charset); text = eolConvert(s, Misc.LINE_SEPARATOR); } return text; } //--------------------- End Interface IsTextual --------------------- @Override public String toString() { return getText(); } public ByteBuffer toBuffer() { if (text != null) { byte[] buf = eolConvert(text).getBytes(); if (buf != null) { int imax = Math.min(buf.length, buffer.limit()); buffer.position(0); buffer.put(buf, 0, imax); while (buffer.remaining() > 0) { buffer.put((byte)0); } if (terminateString) { buffer.position(buffer.position() - 1); buffer.put((byte)0); } } } return buffer; } public EOLType getEolType() { return eolType; } public void setEolType(EOLType type) { if (type != null) eolType = type; } public boolean getStringTerminated() { return terminateString; } public void setStringTerminated(boolean terminated) { terminateString = terminated; } public Charset getCharset() { return charset; } public boolean setCharset(String charsetName) { if (Charset.isSupported(charsetName)) { this.charset = Charset.forName(charsetName); return true; } else { return false; } } public boolean getEditable() { return editable; } public void setEditable(boolean edit) { editable = edit; } private String eolConvert(String s) { if (s != null && s.length() > 0) return s.replaceAll("(\r\n|\n)", EOL.get(eolType)); else return s; } private String eolConvert(String s, String eol) { if (s != null && s.length() > 0 && eol != null && eol.length() > 0) return s.replaceAll("(\r\n|\n)", eol); else return s; } //-------------------------- INNER CLASSES -------------------------- // Ensures a size limit on byte level private class FixedDocument extends RSyntaxDocument { private int maxLength; private RTextArea textArea; FixedDocument(RTextArea text, int length) { super(null); textArea = text; maxLength = length >= 0 ? length : 0; } @Override public void insertString(int offs, String str, AttributeSet a) throws BadLocationException { if (str == null || textArea == null || eolConvert(textArea.getText()).getBytes().length + eolConvert(str).getBytes().length > maxLength) return; super.insertString(offs, str, a); } } }