/*******************************************************************************
* This file is part of logisim-evolution.
*
* logisim-evolution is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* logisim-evolution is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with logisim-evolution. If not, see <http://www.gnu.org/licenses/>.
*
* Original code by Carl Burch (http://www.cburch.com), 2011.
* Subsequent modifications by :
* + Haute École Spécialisée Bernoise
* http://www.bfh.ch
* + Haute École du paysage, d'ingénierie et d'architecture de Genève
* http://hepia.hesge.ch/
* + Haute École d'Ingénierie et de Gestion du Canton de Vaud
* http://www.heig-vd.ch/
* The project is currently maintained by :
* + REDS Institute - HEIG-VD
* Yverdon-les-Bains, Switzerland
* http://reds.heig-vd.ch
*******************************************************************************/
package com.cburch.logisim.std.io;
import java.awt.FontMetrics;
import com.cburch.logisim.data.Value;
import com.cburch.logisim.instance.InstanceData;
class KeyboardData implements InstanceData, Cloneable {
private Value lastClock;
private char[] buffer;
private String str;
private int bufferLength;
private int cursorPos;
private boolean dispValid;
private int dispStart;
private int dispEnd;
public KeyboardData(int capacity) {
lastClock = Value.UNKNOWN;
buffer = new char[capacity];
clear();
}
public void clear() {
bufferLength = 0;
cursorPos = 0;
str = "";
dispValid = false;
dispStart = 0;
dispEnd = 0;
}
@Override
public Object clone() {
try {
KeyboardData ret = (KeyboardData) super.clone();
ret.buffer = this.buffer.clone();
return ret;
} catch (CloneNotSupportedException e) {
return null;
}
}
public boolean delete() {
char[] buf = buffer;
int len = bufferLength;
int pos = cursorPos;
if (pos >= len)
return false;
for (int i = pos + 1; i < len; i++)
buf[i - 1] = buf[i];
bufferLength = len - 1;
str = null;
dispValid = false;
return true;
}
public char dequeue() {
char[] buf = buffer;
int len = bufferLength;
if (len == 0)
return '\0';
char ret = buf[0];
for (int i = 1; i < len; i++)
buf[i - 1] = buf[i];
bufferLength = len - 1;
int pos = cursorPos;
if (pos > 0)
cursorPos = pos - 1;
str = null;
dispValid = false;
return ret;
}
private boolean fits(FontMetrics fm, String str, int w0, int w1, int i0,
int i1, int max) {
if (i0 >= i1)
return true;
int len = str.length();
if (i0 < 0 || i1 > len)
return false;
int w = fm.stringWidth(str.substring(i0, i1));
if (i0 > 0)
w += w0;
if (i1 < str.length())
w += w1;
return w <= max;
}
public char getChar(int pos) {
return pos >= 0 && pos < bufferLength ? buffer[pos] : '\0';
}
public int getCursorPosition() {
return cursorPos;
}
public int getDisplayEnd() {
return dispEnd;
}
public int getDisplayStart() {
return dispStart;
}
public int getNextSpecial(int pos) {
char[] buf = buffer;
int len = bufferLength;
for (int i = pos; i < len; i++) {
char c = buf[i];
if (Character.isISOControl(c))
return i;
}
return -1;
}
public boolean insert(char value) {
char[] buf = buffer;
int len = bufferLength;
if (len >= buf.length)
return false;
int pos = cursorPos;
for (int i = len; i > pos; i--)
buf[i] = buf[i - 1];
buf[pos] = value;
bufferLength = len + 1;
cursorPos = pos + 1;
str = null;
dispValid = false;
return true;
}
public boolean isDisplayValid() {
return dispValid;
}
public boolean moveCursorBy(int delta) {
int len = bufferLength;
int pos = cursorPos;
int newPos = pos + delta;
if (newPos < 0 || newPos > len)
return false;
cursorPos = newPos;
dispValid = false;
return true;
}
public boolean setCursor(int value) {
int len = bufferLength;
if (value > len)
value = len;
int pos = cursorPos;
if (pos == value)
return false;
cursorPos = value;
dispValid = false;
return true;
}
public Value setLastClock(Value newClock) {
Value ret = lastClock;
lastClock = newClock;
return ret;
}
@Override
public String toString() {
String s = str;
if (s != null)
return s;
StringBuilder build = new StringBuilder();
char[] buf = buffer;
int len = bufferLength;
for (int i = 0; i < len; i++) {
char c = buf[i];
build.append(Character.isISOControl(c) ? ' ' : c);
}
str = build.toString();
return str;
}
public void updateBufferLength(int len) {
synchronized (this) {
char[] buf = buffer;
int oldLen = buf.length;
if (oldLen != len) {
char[] newBuf = new char[len];
System.arraycopy(buf, 0, newBuf, 0, Math.min(len, oldLen));
if (len < oldLen) {
if (bufferLength > len)
bufferLength = len;
if (cursorPos > len)
cursorPos = len;
}
buffer = newBuf;
str = null;
dispValid = false;
}
}
}
public void updateDisplay(FontMetrics fm) {
if (dispValid)
return;
int pos = cursorPos;
int i0 = dispStart;
int i1 = dispEnd;
String str = toString();
int len = str.length();
int max = Keyboard.WIDTH - 8 - 4;
if (str.equals("") || fm.stringWidth(str) <= max) {
i0 = 0;
i1 = len;
} else {
// grow to include end of string if possible
int w0 = fm.stringWidth(str.charAt(0) + "m");
int w1 = fm.stringWidth("m");
int w = i0 == 0 ? fm.stringWidth(str) : w0
+ fm.stringWidth(str.substring(i0));
if (w <= max)
i1 = len;
// rearrange start/end so as to include cursor
if (pos <= i0) {
if (pos < i0) {
i1 += pos - i0;
i0 = pos;
}
if (pos == i0 && i0 > 0) {
i0--;
i1--;
}
}
if (pos >= i1) {
if (pos > i1) {
i0 += pos - i1;
i1 = pos;
}
if (pos == i1 && i1 < len) {
i0++;
i1++;
}
}
if (i0 <= 2)
i0 = 0;
// resize segment to fit
if (fits(fm, str, w0, w1, i0, i1, max)) { // maybe should grow
while (fits(fm, str, w0, w1, i0, i1 + 1, max))
i1++;
while (fits(fm, str, w0, w1, i0 - 1, i1, max))
i0--;
} else { // should shrink
if (pos < (i0 + i1) / 2) {
i1--;
while (!fits(fm, str, w0, w1, i0, i1, max))
i1--;
} else {
i0++;
while (!fits(fm, str, w0, w1, i0, i1, max))
i0++;
}
}
if (i0 == 1)
i0 = 0;
}
dispStart = i0;
dispEnd = i1;
dispValid = true;
}
}