Initial version of the project for java studies from 2013.

master
Tomasz Półgrabia 2021-01-03 20:18:57 +01:00
commit 54c68d7375
99 changed files with 10185 additions and 0 deletions

11
.gitignore vendored Normal file
View File

@ -0,0 +1,11 @@
*.class
# Package Files #
*.jar
*.war
*.ear
.idea
.gradle
out
build

View File

@ -0,0 +1,12 @@
apply plugin: 'java'
apply plugin: 'application'
repositories {
mavenCentral()
}
dependencies {
}
mainClassName = 'tpsa.BFInterpreter'

View File

@ -0,0 +1,70 @@
package tpsa;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;
public class BFInterpreter {
static LogManager lm;
public static Logger lg;
static Level lv = Level.OFF;
static {
lm = LogManager.getLogManager();
lm.addLogger(Logger.getLogger("BFInterpreter"));
lg = lm.getLogger("BFInterpreter");
lg.setLevel(lv);
for (Handler h : BFInterpreter.lg.getHandlers())
h.setLevel(lv);
for (Handler h : Logger.getLogger("").getHandlers())
h.setLevel(lv);
for (Handler h : Logger.getLogger("").getHandlers())
h.setLevel(lv);
}
/**
* @param args
*/
public static void main(String[] args) {
Thread t = new Thread(
new BFMachine(
"+[,+[-[>+>+<<-]>[<+>-]+>>++++++++[<-------->-]<-[<[-]>>>+[<+<+>>-]<[>+<-]<[<++>\n"
+ ">>+[<+<->>-]<[>+<-]]>[<]<]>>[-]<<<[[-]<[>>+>+<<<-]>>[<<+>>-]>>++++++++[<-------\n"
+ "->-]<->>++++[<++++++++>-]<-<[>>>+<<[>+>[-]<<-]>[<+>-]>[<<<<<+>>>>++++[<++++++++\n"
+ ">-]>-]<<-<-]>[<<<<[-]>>>>[<<<<->>>>-]]<<++++[<<++++++++>>-]<<-[>>+>+<<<-]>>[<<+\n"
+ ">>-]+>>+++++[<----->-]<-[<[-]>>>+[<+<->>-]<[>+<-]<[<++>>>+[<+<+>>-]<[>+<-]]>[<]\n"
+ "<]>>[-]<<<[[-]<<[>>+>+<<<-]>>[<<+>>-]+>------------[<[-]>>>+[<+<->>-]<[>+<-]<[<\n"
+ "++>>>+[<+<+>>-]<[>+<-]]>[<]<]>>[-]<<<<<------------->>[[-]+++++[<<+++++>>-]<<+>\n"
+ ">]<[>++++[<<++++++++>>-]<-]>]<[-]++++++++[<++++++++>-]<+>]<.[-]+>>+<]>[[-]<]<]"));
try {
t.start();
t.join();
} catch (InterruptedException e) {
BFInterpreter.lg.severe(BFInterpreter.ErrorsFormatter(e));
}
}
public static String ErrorsFormatter(Exception excp) {
StringBuilder sb = new StringBuilder();
String newLine = System.getProperty("line.separator");
sb.append("Exception catched: " + excp.getLocalizedMessage() + newLine);
StackTraceElement[] stack = excp.getStackTrace();
for (int i = 0; i < stack.length; i++) {
sb.append("StackElement(" + i + ") : " + stack[i].getClassName()
+ ", " + stack[i].getFileName() + ":"
+ stack[i].getLineNumber() + ", "
+ stack[i].getMethodName() + newLine);
}
return sb.toString();
}
}

View File

@ -0,0 +1,381 @@
package tpsa;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.Arrays;
import java.util.EmptyStackException;
import java.util.Stack;
import java.util.concurrent.atomic.AtomicBoolean;
import tpsa.exceptions.BFException;
import tpsa.exceptions.BadCallException;
import tpsa.exceptions.MalformedCodeException;
import tpsa.exceptions.OutOfMemoryPointer;
import tpsa.streams.JTextPaneInputStream;
import tpsa.streams.JTextPaneOutputStream;
/**
* Brainfuck is an esoteric programming language noted for its extreme minimalism.
* It is a Turing tarpit, designed to challenge and amuse programmers,
* and was not made to be suitable for practical use. It was created in 1993 by Urban Müller.
* The name of the language is generally not capitalized except at the start of a sentence,
* although it is a proper noun.
*
* Język ma 8 mnemoników (patrz mnemoniki assemblera)
*
* > zwiększa wskaźnik o 1 ++p
* < zmniejsza wskaźnik o 1 --p
* + zwiększa o 1 w bieżącej pozycji ++(*p)
* - zmniejsza o 1 w bieżącej pozycji --(*p)
* . wyświetla znak w bieżącej pozycji (ASCII) putchar(*p)
* , pobiera znak i wstawia go w bieżącej pozycji (ASCII) *p=getchar()
* [ skacze bezpośrednio za odpowiadający mu ], jeśli w
* bieżącej pozycji znajduje się 0 while(*p){
* ] skacze do odpowiadającego mu [ }
*
*/
/**
* Klasa główna maszyny BF. Jest ona odpowiedzialna, za stworzenie VM dla BF i
* interpretowanie kodu BF.
*
* @author Duga Eye
*/
public class BFMachine implements Runnable {
public static int defaultMemorySize = 0x10000;
/**
* Kod, dla maszyny wirtualnej BF
*/
private Runnable breakpointHandler;
private byte[] code;
private int codePointer = 0;
private String error;
private InputStreamReader iStreamReader = null;
private InputStream iS = null;
private Object lock = new Object();
private AtomicBoolean machineWorking = new AtomicBoolean(false);
private byte[] memory;
private int memoryPointer = 0;
private OutputStreamWriter oStream = null;
private AtomicBoolean paused = new AtomicBoolean(false);
private Runnable postHandler;
private Stack<Integer> stackExecution = new Stack<Integer>();
public void reset() {
memoryPointer = memory.length / 2;
Arrays.fill(memory, (byte) 0);
try {
iS.reset();
} catch (IOException excp) {
}
codePointer = 0;
}
private Runnable stepHandler;
private AtomicBoolean stepping = new AtomicBoolean(false);
public BFMachine(JTextPaneInputStream is, JTextPaneOutputStream os,
String code, int sizeOfMemory) {
this.code = code.getBytes();
memory = new byte[sizeOfMemory];
memoryPointer = sizeOfMemory / 2;
iS = is;
oStream = new OutputStreamWriter(os);
iStreamReader = new InputStreamReader(is);
}
/**
* Domyślnie program startuje z wskaźnikiem pamięci w środku pamięci
* operacynej
*
* @param code
* Kod, który wykonywany jest przez maszynę BF
*/
public BFMachine(String code) {
this(code, defaultMemorySize);
}
/**
* Domyślnie program startuje z wskaźnikiem pamięci w środku pamięci
* operacynej
*
* @param code
* Kod, który wykonywany jest przez maszynę BF
* @param sizeOfMemory
* rozmiar pamięci operacyjnej maszyny BF
*/
public BFMachine(String code, int sizeOfMemory) {
this.code = code.getBytes();
memory = new byte[sizeOfMemory];
memoryPointer = sizeOfMemory / 2;
error = "";
}
public Runnable getBreakpointHandler() {
return breakpointHandler;
}
public String getError() {
if (error == null)
return new String();
return new String(error);
}
public byte[] getMemory() {
return memory;
}
public int getMemoryPointer() {
return memoryPointer;
}
public int getPosition() {
return codePointer;
}
public Runnable getStepHandler() {
return stepHandler;
}
public boolean getStepping() {
return stepping.get();
}
public boolean isWorking() {
return machineWorking.get();
}
/**
* Zatrzymuje pracę maszynę BF
*/
public void pause() {
paused.set(true);
}
/**
* Wznawia pracę maszyny BF
*/
public void resume() {
paused.set(false);
synchronized (lock) {
lock.notify();
}
}
/**
* Przewija wskaźnik kodu na pozycję o 1 dalej niż jest koniec
* odpowiadającemu mu znakowi końca pętli
*
* @throws BadCallException
* W miejscu wskaźnika BF nie ma znaku początku pętli
* @throws MalformedCodeException
* Kod podany dla maszyny BF jest niepoprawny
*/
private void rewindTheLoopToTheEnd() throws BadCallException,
MalformedCodeException {
if (code[codePointer] != '[')
throw new BadCallException("Nie ma '[' w miejscu startu",
codePointer);
// System.err.println("Wchodzę w pozycji: " + codePointer);
int stack = 1;
codePointer++;
while (stack > 0) {
while (code[codePointer] != '[' && code[codePointer] != ']') {
// System.err.println("Pozycja: " + codePointer + ", char: " +
// code[codePointer]);
if (++codePointer >= code.length)
throw new MalformedCodeException(
"Nie wiem jakim cudem, ale pointer wyszedł poza kod",
codePointer);
}
// System.err.println("Pozycja: " + codePointer + ", char: " +
// code[codePointer]);
switch (code[codePointer]) {
case (byte) 0xcc:
/*
* if (breakpointHandled.get() == false) return; else {
* breakpointHandled.set(true); }
*/
break;
case '[':
++stack;
break;
case ']':
--stack;
break;
}
codePointer++;
}
// System.err.println("Znak wyjścia: " + codePointer + ", char: " +
// code[codePointer]);
}
@Override
public void run() {
memoryPointer = memory.length / 2;
codePointer = 0;
BFInterpreter.lg.info("Machine starting");
BFInterpreter.lg.finest("Code" + new String(code));
machineWorking.set(true);
try {
while (codePointer < code.length && machineWorking.get()) {
if (paused.get() || stepping.get()) {
if (stepHandler != null && stepping.get()) {
BFInterpreter.lg.fine("Stepping item");
System.err.println("Joł");
stepHandler.run();
}
synchronized (lock) {
lock.wait();
}
}
// BFInterpreter.lg.finest("Executing code at " + codePointer);
switch (code[codePointer]) {
case 'b':
if (breakpointHandler != null) {
breakpointHandler.run();
synchronized (lock) {
lock.wait();
}
} else
return;
break;
case '>':
if (++memoryPointer >= memory.length)
throw new OutOfMemoryPointer(
"Pointer wyszedł poza pamięć", codePointer);
break;
case '<':
if (--memoryPointer < 0)
throw new OutOfMemoryPointer(
"Pointer wyszedł poza pamięć", codePointer);
break;
case '+':
++memory[memoryPointer];
break;
case '-':
--memory[memoryPointer];
break;
case '.':
BFInterpreter.lg.finest("Priting char");
if (oStream == null) {
System.out.print(memory[memoryPointer]);
} else {
oStream.write(memory[memoryPointer]);
}
break;
case ',':
BFInterpreter.lg.fine("Reading char from input");
int c = (iStreamReader == null) ? System.in.read() : iStreamReader
.read();
BFInterpreter.lg.fine("Read input code " + c);
memory[memoryPointer] = (byte) c;
BFInterpreter.lg.fine("Read char");
break;
case '[':
// System.err.println("Wartość w komórce: " +
// (int)memory[memoryPointer]);
if (memory[memoryPointer] > 0)
stackExecution.push(codePointer);
else {
rewindTheLoopToTheEnd();
continue;
}
break;
case ']':
int pos = stackExecution.pop();
codePointer = pos;
continue;
}
codePointer++;
Thread.yield();
}
if (oStream != null)
oStream.flush();
// oStream.close();
} catch (BFException excp) {
String message = excp.getMessage();
if (message == null)
message = new String("Exception");
setError(excp.getClass().getName() + ": " + message + ", offset: "
+ excp.getOffset());
} catch (EmptyStackException excp) {
setError(excp.getClass().getName()
+ "Stary, klamerki się nie zgadzają");
} catch (IOException excp) {
setError("No nic, IOException..., cokolwiek by to nie znaczyło");
} catch (InterruptedException excp) {
BFInterpreter.lg.severe(BFInterpreter.ErrorsFormatter(excp));
}
if (postHandler != null && machineWorking.get() == true)
postHandler.run();
machineWorking.set(false);
BFInterpreter.lg.finest("Machine has ended");
}
public void setBreakpointHandler(Runnable breakpointHandler) {
this.breakpointHandler = breakpointHandler;
}
public void setCode(String code) {
this.code = code.getBytes();
}
public void setError(String error) {
this.error = new String(error);
}
public void setFinishHandler(Runnable runnable) {
this.postHandler = runnable;
}
public void setStepHandler(Runnable stepHandler) {
this.stepHandler = stepHandler;
}
public void setStepping(boolean b) {
this.stepping.set(b);
}
/**
* Definitywnie kończy pracę maszyny (nie da się wznowić)
*
* @see pause
*/
public void stop() {
// definitywnie kończy pracę, nie da się wznowić
machineWorking.set(false);
}
public byte[] getCode() {
return code;
}
public void setCode(byte[] code) {
this.code = code;
}
}

View File

@ -0,0 +1,27 @@
package tpsa.exceptions;
/**
* Ogólny exception dla BF
*
* @author Duga Eye
*
*/
public class BFException extends Exception {
/**
*
*/
private static final long serialVersionUID = -1955781345162254359L;
private int offset;
public BFException(String message, int offset) {
super(message);
this.offset = offset;
}
public int getOffset() {
return offset;
}
}

View File

@ -0,0 +1,21 @@
package tpsa.exceptions;
/**
* Wyjątek rzucany gdy funkcja rewindTheLoopToTheEnd zostanie wywołana nie
* wtedy, gdy codePointer będzie wskazywał na '['
*
* @author Duga Eye
*
*/
public class BadCallException extends BFException {
/**
*
*/
private static final long serialVersionUID = -2134149027354550342L;
public BadCallException(String message, int offset) {
super(message, offset);
}
}

View File

@ -0,0 +1,21 @@
package tpsa.exceptions;
/**
* W zasadzie ten wyjątek nigdy nie powinien wystąpić: "Nie wiem jakim cudem,
* ale pointer wyszedł poza kod"
*
* @author Duga Eye
*
*/
public class MalformedCodeException extends BFException {
/**
*
*/
private static final long serialVersionUID = 5072506605351465843L;
public MalformedCodeException(String message, int offset) {
super(message, offset);
}
}

View File

@ -0,0 +1,14 @@
package tpsa.exceptions;
public class NotImplementedException extends BFException {
/**
*
*/
private static final long serialVersionUID = 8190551201861687817L;
public NotImplementedException(String message, int offset) {
super(message, offset);
}
}

View File

@ -0,0 +1,20 @@
package tpsa.exceptions;
/**
* Wskaźnik pamięci wyszedł poza pamięć
*
* @author Duga Eye
*
*/
public class OutOfMemoryPointer extends BFException {
/**
*
*/
private static final long serialVersionUID = 6323824785754255254L;
public OutOfMemoryPointer(String message, int offset) {
super(message, offset);
}
}

View File

@ -0,0 +1,108 @@
package tpsa.streams;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.Semaphore;
import java.util.logging.Logger;
import javax.swing.SwingUtilities;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Style;
import javax.swing.text.StyledDocument;
import tpsa.BFInterpreter;
/**
* Klasa dająca strumień wejściowy z JTextPane dla BF maszyny
*
* @author Duga Eye
*
*/
public class JTextPaneInputStream extends InputStream implements
DocumentListener {
private boolean eof = false;
private Logger lg = Logger.getLogger("BF");
private Semaphore sem = new Semaphore(0);
private StringBuilder string = new StringBuilder();
public JTextPaneInputStream() {
}
public void reset() {
eof = false;
string.replace(0, string.length(), "");
try {
if (sem.availablePermits() > 0)
sem.acquire(sem.availablePermits());
} catch (InterruptedException excp) {
}
}
@Override
public void changedUpdate(DocumentEvent e) {
}
@Override
public void insertUpdate(final DocumentEvent e) {
try {
lg.info("Strumień (handler) otrzymał nowe powiadomienie o wprowadzonych danych");
String s = e.getDocument().getText(e.getOffset(), e.getLength());
string.append(s);
sem.release(s.length());
lg.info("Wartość semafora: " + sem.availablePermits());
lg.info("Strumień dodał parę bajtów do bufora");
final StyledDocument doc = (StyledDocument) e.getDocument();
final Style set = doc.getStyle("INPUT");
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
doc.setParagraphAttributes(e.getOffset(), e.getLength(),
set, false);
}
});
} catch (BadLocationException excp) {
BFInterpreter.lg.severe(BFInterpreter.ErrorsFormatter(excp));
}
}
@Override
public int read() throws IOException {
if (eof == true) {
return -1;
}
try {
lg.info("Strumień ma dać jeden bajt");
sem.acquire();
lg.info("Strumień uzyskał bajt na semaforze");
String c = string.substring(0, 1);
char c2 = c.charAt(0);
Character c3 = '\u001a';
if (c3.equals(c2)) {
lg.info("ERROR SUCCESS");
eof = true;
return -1;
}
string.delete(0, 1);
lg.info("Strumień usunął wczytany bajt z bufora");
return c2;
} catch (Exception e) {
return -1;
}
}
@Override
public void removeUpdate(DocumentEvent e) {
}
}

View File

@ -0,0 +1,51 @@
package tpsa.streams;
import java.io.IOException;
import java.io.OutputStream;
import java.util.logging.Logger;
import javax.swing.JTextPane;
import javax.swing.text.BadLocationException;
import javax.swing.text.Style;
import javax.swing.text.StyledDocument;
import tpsa.BFInterpreter;
/**
* Klasa strumień wyjściowy do JTextPane dla BF maszyny
*
* @author Duga Eye
*
*/
public class JTextPaneOutputStream extends OutputStream {
private Logger lg = Logger.getLogger("BF");
private JTextPane pane;
public JTextPaneOutputStream() {
}
public JTextPane getPane() {
return pane;
}
public void setPane(JTextPane pane) {
this.pane = pane;
}
@Override
public void write(int b) throws IOException {
lg.finest("Writing byte :" + b);
StyledDocument doc = (StyledDocument) pane.getDocument();
char c = (char) b;
String s = "" + c;
Style style = doc.getStyle("OUTPUT");
try {
doc.insertString(doc.getLength(), s, style);
} catch (BadLocationException excp) {
BFInterpreter.lg.severe(BFInterpreter.ErrorsFormatter(excp));
}
}
}

1
LICENSE.md Normal file
View File

@ -0,0 +1 @@
Róbta co chceta z tym kodem, tylko nie publikujcie tego z moim imieniem, nazwiskiem, czy nickiem.

13
README.md Normal file
View File

@ -0,0 +1,13 @@
# Purpose
It was a test project for java studies (to complete the course). Except of putting it under the gradle control (it
wasn't), whole code is original from those times. I will try to translate original comments, naming convention to
non-polish.
# Technology
Ancient swing gui with some custom helpers from other authors
- https://github.com/sporst/splib
- https://github.com/Konloch/bytecode-viewer
HexEditor (com.jhe.hexed) and other (tv.ports.*) are not mine, just reused.

View File

@ -0,0 +1,2 @@
Pisze kod, dla inputa, taki że kod wygenerowany generuje wpisany input
,[>>++++++[-<+++++++>]<+<[->.<]>+++.<++++[->++++<]>.>,]

12
bfIDE/build.gradle Normal file
View File

@ -0,0 +1,12 @@
apply plugin: 'java'
apply plugin: 'application'
repositories {
mavenCentral()
}
dependencies {
implementation project(':BFInterpreter')
}
mainClassName = 'tpsa.BrainFuckIDE'

1
bfIDE/failure.bf Normal file
View File

@ -0,0 +1 @@
[]]

3
bfIDE/hacker.bf Normal file
View File

@ -0,0 +1,3 @@
+++[>+++++<-]>[>+>+++>+>++>+++++>++<[++<]>---]>->-.[>++>+<<--]>--.--.+.>>>++.<<
.<------.+.+++++.>>-.<++++.<--.>>>.<<---.<.-->-.>+.[+++++.---<]>>[.--->]<<.<+.+
+.++>+++[.<][.]<++.

BIN
bfIDE/hello.bf Normal file

Binary file not shown.

1
bfIDE/infinite.bf Normal file
View File

@ -0,0 +1 @@
+[]

1
bfIDE/memoryOffset.bf Normal file
View File

@ -0,0 +1 @@
+[>+]

3
bfIDE/squares.bf Normal file
View File

@ -0,0 +1,3 @@
++++[>+++++<-]>[<+++++>-]+<+[>[>+>+<<-]++>>[<<+>>-]>>>[-]++>[-]+>>>+[[-]++++++>
>>]<<<[[<++++++++<++>>-]+<.<[>----<-]<]<<[>>>>>[>>>[-]+++++++++<[>-<-]+++++++++
>[-[<->-]+[<<<]]<[>+<-]>]<<-]<<-]

View File

@ -0,0 +1,324 @@
package com.jhe.hexed;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import javax.swing.JPanel;
import javax.swing.JScrollBar;
/**
* Created by IntelliJ IDEA.
* User: laullon
* Date: 08-abr-2003
* Time: 13:21:09
*/
public class JHexEditor extends JPanel implements FocusListener,AdjustmentListener,MouseWheelListener
{
/**
*
*/
private static final long serialVersionUID = 2289328616534802372L;
byte[] buff;
public int cursor;
protected static Font font=new Font("Monospaced",0,12);
protected int border=2;
public boolean DEBUG=false;
private JPanel panel;
private JScrollBar sb;
private int inicio=0;
private int lineas=10;
public JHexEditor(byte[] buff)
{
super();
this.buff=buff;
this.addMouseWheelListener(this);
sb=new JScrollBar(JScrollBar.VERTICAL);
sb.addAdjustmentListener(this);
// sb.setMinimum(0);
// sb.setMaximum(buff.length/getLineas());
JPanel p1,p2,p3;
//centro
p1=new JPanel(new BorderLayout(1,1));
p1.add(new JHexEditorHEX(this),BorderLayout.CENTER);
p1.add(new Columnas(),BorderLayout.NORTH);
// izq.
p2=new JPanel(new BorderLayout(1,1));
p2.add(new Filas(),BorderLayout.CENTER);
p2.add(new Caja(),BorderLayout.NORTH);
// der
p3=new JPanel(new BorderLayout(1,1));
p3.add(sb,BorderLayout.EAST);
p3.add(new JHexEditorASCII(this),BorderLayout.CENTER);
p3.add(new Caja(),BorderLayout.NORTH);
panel=new JPanel();
panel.setLayout(new BorderLayout(1,1));
panel.add(p1,BorderLayout.CENTER);
panel.add(p2,BorderLayout.WEST);
panel.add(p3,BorderLayout.EAST);
this.setLayout(new BorderLayout(1,1));
this.add(panel,BorderLayout.CENTER);
}
public void paint(Graphics g)
{
FontMetrics fn=getFontMetrics(font);
Rectangle rec=this.getBounds();
lineas=(rec.height/fn.getHeight())-1;
int n=(buff.length/16)-1;
if(lineas>n) { lineas=n; inicio=0; }
sb.setValues(getInicio(),+getLineas(),0,buff.length/16);
sb.setValueIsAdjusting(true);
super.paint(g);
}
protected void actualizaCursor()
{
int n=(cursor/16);
System.out.print("- "+inicio+"<"+n+"<"+(lineas+inicio)+"("+lineas+")");
if(n<inicio) inicio=n;
else if(n>=inicio+lineas) inicio=n-(lineas-1);
System.out.println(" - "+inicio+"<"+n+"<"+(lineas+inicio)+"("+lineas+")");
repaint();
}
protected int getInicio()
{
return inicio;
}
protected int getLineas()
{
return lineas;
}
protected void fondo(Graphics g,int x,int y,int s)
{
FontMetrics fn=getFontMetrics(font);
g.fillRect(((fn.stringWidth(" ")+1)*x)+border,(fn.getHeight()*y)+border,((fn.stringWidth(" ")+1)*s),fn.getHeight()+1);
}
protected void cuadro(Graphics g,int x,int y,int s)
{
FontMetrics fn=getFontMetrics(font);
g.drawRect(((fn.stringWidth(" ")+1)*x)+border,(fn.getHeight()*y)+border,((fn.stringWidth(" ")+1)*s),fn.getHeight()+1);
}
protected void printString(Graphics g,String s,int x,int y)
{
FontMetrics fn=getFontMetrics(font);
g.drawString(s,((fn.stringWidth(" ")+1)*x)+border,((fn.getHeight()*(y+1))-fn.getMaxDescent())+border);
}
public void focusGained(FocusEvent e)
{
this.repaint();
}
public void focusLost(FocusEvent e)
{
this.repaint();
}
public void adjustmentValueChanged(AdjustmentEvent e)
{
inicio=e.getValue();
if(inicio<0) inicio=0;
repaint();
}
public void mouseWheelMoved(MouseWheelEvent e)
{
inicio+=(e.getUnitsToScroll());
if((inicio+lineas)>=buff.length/16) inicio=(buff.length/16)-lineas;
if(inicio<0) inicio=0;
repaint();
}
public void keyPressed(KeyEvent e)
{
switch(e.getKeyCode())
{
case 33: // rep
if(cursor>=(16*lineas)) cursor-=(16*lineas);
actualizaCursor();
break;
case 34: // fin
if(cursor<(buff.length-(16*lineas))) cursor+=(16*lineas);
actualizaCursor();
break;
case 35: // fin
cursor=buff.length-1;
actualizaCursor();
break;
case 36: // ini
cursor=0;
actualizaCursor();
break;
case 37: // <--
if(cursor!=0) cursor--;
actualizaCursor();
break;
case 38: // <--
if(cursor>15) cursor-=16;
actualizaCursor();
break;
case 39: // -->
if(cursor!=(buff.length-1)) cursor++;
actualizaCursor();
break;
case 40: // -->
if(cursor<(buff.length-16)) cursor+=16;
actualizaCursor();
break;
}
}
private class Columnas extends JPanel
{
/**
*
*/
private static final long serialVersionUID = -1734199617526339842L;
public Columnas()
{
this.setLayout(new BorderLayout(1,1));
}
public Dimension getPreferredSize()
{
return getMinimumSize();
}
public Dimension getMinimumSize()
{
Dimension d=new Dimension();
FontMetrics fn=getFontMetrics(font);
int h=fn.getHeight();
int nl=1;
d.setSize(((fn.stringWidth(" ") + 1) * +((16 * 3) - 1))
+ (border * 2) + 1, h * nl + (border * 2) + 1);
return d;
}
public void paint(Graphics g)
{
Dimension d=getMinimumSize();
g.setColor(Color.white);
g.fillRect(0,0,d.width,d.height);
g.setColor(Color.black);
g.setFont(font);
for(int n=0;n<16;n++)
{
if(n==(cursor%16)) cuadro(g,n*3,0,2);
String s="00"+Integer.toHexString(n);
s=s.substring(s.length()-2);
printString(g,s,n*3,0);
}
}
}
private class Caja extends JPanel
{
/**
*
*/
private static final long serialVersionUID = -6124062720565016834L;
public Dimension getPreferredSize()
{
return getMinimumSize();
}
public Dimension getMinimumSize()
{
Dimension d=new Dimension();
FontMetrics fn=getFontMetrics(font);
int h=fn.getHeight();
d.setSize((fn.stringWidth(" ")+1)+(border*2)+1,h+(border*2)+1);
return d;
}
}
private class Filas extends JPanel
{
/**
*
*/
private static final long serialVersionUID = 8797347523486018051L;
public Filas()
{
this.setLayout(new BorderLayout(1,1));
}
public Dimension getPreferredSize()
{
return getMinimumSize();
}
public Dimension getMinimumSize()
{
Dimension d=new Dimension();
FontMetrics fn=getFontMetrics(font);
int h=fn.getHeight();
int nl=getLineas();
d.setSize((fn.stringWidth(" ")+1)*(8)+(border*2)+1,h*nl+(border*2)+1);
return d;
}
public void paint(Graphics g)
{
Dimension d=getMinimumSize();
g.setColor(Color.white);
g.fillRect(0,0,d.width,d.height);
g.setColor(Color.black);
g.setFont(font);
int ini=getInicio();
int fin=ini+getLineas();
int y=0;
for(int n=ini;n<fin;n++)
{
if(n==(cursor/16)) cuadro(g,0,y,8);
String s="0000000000000"+Integer.toHexString(n);
s=s.substring(s.length()-8);
printString(g,s,0,y++);
}
}
}
public byte[] getBuff() {
return buff;
}
public void setBuff(byte[] buff) {
this.buff = buff;
}
}

View File

@ -0,0 +1,162 @@
package com.jhe.hexed;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.JComponent;
/**
* Created by IntelliJ IDEA.
* User: laullon
* Date: 09-abr-2003
* Time: 12:47:18
*/
public class JHexEditorASCII extends JComponent implements MouseListener,KeyListener
{
/**
*
*/
private static final long serialVersionUID = 5505374841731053461L;
private JHexEditor he;
public JHexEditorASCII(JHexEditor he)
{
this.he=he;
addMouseListener(this);
addKeyListener(this);
addFocusListener(he);
}
public Dimension getPreferredSize()
{
debug("getPreferredSize()");
return getMinimumSize();
}
public Dimension getMinimumSize()
{
debug("getMinimumSize()");
Dimension d=new Dimension();
FontMetrics fn = getFontMetrics(JHexEditor.font);
int h=fn.getHeight();
int nl=he.getLineas();
d.setSize((fn.stringWidth(" ")+1)*(16)+(he.border*2)+1,h*nl+(he.border*2)+1);
return d;
}
public void paint(Graphics g)
{
debug("paint("+g+")");
debug("cursor="+he.cursor+" buff.length="+he.buff.length);
Dimension d=getMinimumSize();
g.setColor(Color.white);
g.fillRect(0,0,d.width,d.height);
g.setColor(Color.black);
g.setFont(JHexEditor.font);
//datos ascii
int ini=he.getInicio()*16;
int fin=ini+(he.getLineas()*16);
if(fin>he.buff.length) fin=he.buff.length;
int x=0;
int y=0;
for(int n=ini;n<fin;n++)
{
if(n==he.cursor)
{
g.setColor(Color.blue);
if(hasFocus()) he.fondo(g,x,y,1); else he.cuadro(g,x,y,1);
if(hasFocus()) g.setColor(Color.white); else g.setColor(Color.black);
} else
{
g.setColor(Color.black);
}
String s=""+new Character((char)he.buff[n]);
if((he.buff[n]<20)||(he.buff[n]>126)) s=""+(char)16;
he.printString(g,s,(x++),y);
if(x==16)
{
x=0;
y++;
}
}
}
private void debug(String s)
{
if(he.DEBUG) System.out.println("JHexEditorASCII ==> "+s);
}
// calcular la posicion del raton
public int calcularPosicionRaton(int x,int y)
{
FontMetrics fn = getFontMetrics(JHexEditor.font);
x=x/(fn.stringWidth(" ")+1);
y=y/fn.getHeight();
debug("x="+x+" ,y="+y);
return x+((y+he.getInicio())*16);
}
// mouselistener
public void mouseClicked(MouseEvent e)
{
debug("mouseClicked("+e+")");
he.cursor=calcularPosicionRaton(e.getX(),e.getY());
this.requestFocus();
he.repaint();
}
public void mousePressed(MouseEvent e)
{
}
public void mouseReleased(MouseEvent e)
{
}
public void mouseEntered(MouseEvent e)
{
}
public void mouseExited(MouseEvent e)
{
}
//KeyListener
public void keyTyped(KeyEvent e)
{
debug("keyTyped("+e+")");
he.buff[he.cursor]=(byte)e.getKeyChar();
if(he.cursor!=(he.buff.length-1)) he.cursor++;
he.repaint();
}
public void keyPressed(KeyEvent e)
{
debug("keyPressed("+e+")");
he.keyPressed(e);
}
public void keyReleased(KeyEvent e)
{
debug("keyReleased("+e+")");
}
public boolean isFocusTraversable()
{
return true;
}
}

View File

@ -0,0 +1,188 @@
package com.jhe.hexed;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.JComponent;
/**
* Created by IntelliJ IDEA.
* User: laullon
* Date: 09-abr-2003
* Time: 12:47:32
*/
public class JHexEditorHEX extends JComponent implements MouseListener,KeyListener
{
/**
*
*/
private static final long serialVersionUID = -4919858660968739876L;
private JHexEditor he;
private int cursor=0;
public JHexEditorHEX(JHexEditor he)
{
this.he=he;
addMouseListener(this);
addKeyListener(this);
addFocusListener(he);
}
public Dimension getPreferredSize()
{
debug("getPreferredSize()");
return getMinimumSize();
}
public Dimension getMaximumSize()
{
debug("getMaximumSize()");
return getMinimumSize();
}
public Dimension getMinimumSize()
{
debug("getMinimumSize()");
Dimension d=new Dimension();
FontMetrics fn = getFontMetrics(JHexEditor.font);
int h=fn.getHeight();
int nl=he.getLineas();
d.setSize(((fn.stringWidth(" ")+1)*+((16*3)-1))+(he.border*2)+1,h*nl+(he.border*2)+1);
return d;
}
public void paint(Graphics g)
{
debug("paint("+g+")");
debug("cursor="+he.cursor+" buff.length="+he.buff.length);
Dimension d=getMinimumSize();
g.setColor(Color.white);
g.fillRect(0,0,d.width,d.height);
g.setColor(Color.black);
g.setFont(JHexEditor.font);
int ini=he.getInicio()*16;
int fin=ini+(he.getLineas()*16);
if(fin>he.buff.length) fin=he.buff.length;
//datos hex
int x=0;
int y=0;
for(int n=ini;n<fin;n++)
{
if(n==he.cursor)
{
if(hasFocus())
{
g.setColor(Color.black);
he.fondo(g,(x*3),y,2);
g.setColor(Color.blue);
he.fondo(g,(x*3)+cursor,y,1);
} else
{
g.setColor(Color.blue);
he.cuadro(g,(x*3),y,2);
}
if(hasFocus()) g.setColor(Color.white); else g.setColor(Color.black);
} else
{
g.setColor(Color.black);
}
String s=("0"+Integer.toHexString(he.buff[n]));
s=s.substring(s.length()-2);
he.printString(g,s,((x++)*3),y);
if(x==16)
{
x=0;
y++;
}
}
}
private void debug(String s)
{
if(he.DEBUG) System.out.println("JHexEditorHEX ==> "+s);
}
// calcular la posicion del raton
public int calcularPosicionRaton(int x,int y)
{
FontMetrics fn = getFontMetrics(JHexEditor.font);
x=x/((fn.stringWidth(" ")+1)*3);
y=y/fn.getHeight();
debug("x="+x+" ,y="+y);
return x+((y+he.getInicio())*16);
}
// mouselistener
public void mouseClicked(MouseEvent e)
{
debug("mouseClicked("+e+")");
he.cursor=calcularPosicionRaton(e.getX(),e.getY());
this.requestFocus();
he.repaint();
}
public void mousePressed(MouseEvent e)
{
}
public void mouseReleased(MouseEvent e)
{
}
public void mouseEntered(MouseEvent e)
{
}
public void mouseExited(MouseEvent e)
{
}
//KeyListener
public void keyTyped(KeyEvent e)
{
debug("keyTyped("+e+")");
char c=e.getKeyChar();
if(((c>='0')&&(c<='9'))||((c>='A')&&(c<='F'))||((c>='a')&&(c<='f')))
{
char[] str=new char[2];
String n="00"+Integer.toHexString((int)he.buff[he.cursor]);
if(n.length()>2) n=n.substring(n.length()-2);
str[1-cursor]=n.charAt(1-cursor);
str[cursor]=e.getKeyChar();
he.buff[he.cursor]=(byte)Integer.parseInt(new String(str),16);
if(cursor!=1) cursor=1;
else if(he.cursor!=(he.buff.length-1)){ he.cursor++; cursor=0;}
he.actualizaCursor();
}
}
public void keyPressed(KeyEvent e)
{
debug("keyPressed("+e+")");
he.keyPressed(e);
}
public void keyReleased(KeyEvent e)
{
debug("keyReleased("+e+")");
}
public boolean isFocusTraversable()
{
return true;
}
}

View File

@ -0,0 +1,49 @@
package com.jhe.hexed;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.util.Arrays;
import javax.swing.JFrame;
/**
* Created by IntelliJ IDEA.
* User: laullon
* Date: 08-abr-2003
* Time: 13:16:06
*/
public class Test extends WindowAdapter
{
private JFrame win;
public Test() throws IOException
{
byte[] ar;
ar=new byte[16*16*100];
Arrays.fill(ar,(byte)0);
//ByteArrayOutputStream bos=new ByteArrayOutputStream();
//ObjectOutputStream oos=new ObjectOutputStream(bos);
//oos.writeObject("dfasnvcxnz.,mvnmc,xznvmcxzmnvcmxzcccbnxz cz hajk vc jbcvj xbnzvc sbj cvxz,bcxjnzbcvjhs avcjz cxmzncvxz ");
//ar=bos.toByteArray();
win=new JFrame();
win.getContentPane().add(new JHexEditor(ar));
win.addWindowListener(this);
win.pack();
win.setVisible(true);
}
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
public static void main(String arg[]) throws IOException
{
new Test();
}
}

View File

@ -0,0 +1,512 @@
package tpsa;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Event;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.ContainerEvent;
import java.awt.event.ContainerListener;
import java.awt.event.KeyEvent;
import java.io.File;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTextPane;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.event.UndoableEditListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;
import tpsa.highlighters.SyntaxHighlighter;
import tpsa.streams.JTextPaneInputStream;
import tpsa.streams.JTextPaneOutputStream;
import tv.porst.jhexview.JHexView;
import tv.porst.jhexview.JHexView.DefinitionStatus;
import tv.porst.jhexview.SimpleDataProvider;
/**
* Zakładka tego ide. Każda zakładka posiada swoją maszynę
*
* @author Duga Eye
*
*/
public class BFTab extends JPanel implements CaretListener, ComponentListener,
ActionListener, ContainerListener {
/**
*
*/
private static final long serialVersionUID = 3256469085182527862L;
protected static final double startSplitterPosition = 0.5;
/**
* JTextPane w którym umieszczamy kod BF
*/
private JTextPane codePanel;
private File file;
private Style foundStyle;
private SyntaxHighlighter high;
private String lastCodeWorking;
private BFMachine machine;
private JScrollPane pane;
private JTextPane paneResult;
/**
* Label, który pokazuje aktualną pozycję w tekście
*/
private JLabel positionLabel;
private TextLineNumber rows;
private JScrollPane scrollPaneResult;
private JPanel status;
private JLabel statusLabel;
private JTextPane memoryPanel = new JTextPane();
private JSplitPane splitPane1;
private Timer t;
private JHexView editor;
/**
* @param undoListener
* undo/redo listener
* @param pauseAction
* pause akcja
* @param resumeAction
* resume akcja
* @param stepAction
* step akcja
* @param stopAction
* stop akcja
* @param runAction
* run akcja
* @param undoAction
* undo akcja
* @param redoAction
* redo akcja
*/
public BFTab(UndoableEditListener undoListener, Action pauseAction,
Action resumeAction, Action stepAction, Action stopAction,
Action runAction, Action undoAction, Action redoAction) {
t = new Timer(2000, this);
lastCodeWorking = new String();
codePanel = new JTextPane();
pane = new JScrollPane(codePanel);
rows = new TextLineNumber(codePanel);
status = new JPanel();
status.setLayout(new BorderLayout());
statusLabel = new JLabel("Ready");
positionLabel = new JLabel("0:0");
status.add(statusLabel, BorderLayout.WEST);
status.add(positionLabel, BorderLayout.EAST);
StyledDocument doc = (StyledDocument) codePanel.getDocument();
doc.addDocumentListener(rows);
doc.addUndoableEditListener(undoListener);
codePanel.addCaretListener(this);
pane.setRowHeaderView(rows);
pane.setFocusable(true);
KeyStroke undoKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_Z,
Event.CTRL_MASK);
KeyStroke redoKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_Y,
Event.CTRL_MASK);
codePanel.getInputMap().put(undoKeyStroke, "undo");
codePanel.getInputMap().put(redoKeyStroke, "redo");
codePanel.getActionMap().put("undo", undoAction);
codePanel.getActionMap().put("redo", redoAction);
KeyStroke runWithoutStroke = KeyStroke.getKeyStroke(KeyEvent.VK_F9,
Event.CTRL_MASK);
KeyStroke stopStroke = KeyStroke.getKeyStroke(KeyEvent.VK_F4,
Event.CTRL_MASK);
codePanel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
runWithoutStroke, "RunWithoutDebug");
codePanel.getActionMap().put("RunWithoutDebug", runAction);
codePanel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
stopStroke, "StopAction");
codePanel.getActionMap().put("StopAction", stopAction);
KeyStroke stepStroke = KeyStroke.getKeyStroke(KeyEvent.VK_F6,
Event.CTRL_MASK);
codePanel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
stepStroke, "stepAction");
codePanel.getActionMap().put("stepAction", stepAction);
// panel2.add(pane);
paneResult = new JTextPane();
KeyStroke eofStroke = KeyStroke.getKeyStroke(KeyEvent.VK_D,
Event.CTRL_MASK);
paneResult.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
eofStroke, "EOF");
KeyStroke pauseStroke = KeyStroke.getKeyStroke(KeyEvent.VK_F5,
Event.CTRL_MASK);
codePanel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
pauseStroke, "pauseAction");
codePanel.getActionMap().put("pauseAction", pauseAction);
KeyStroke resumeStroke = KeyStroke.getKeyStroke(KeyEvent.VK_F7,
Event.CTRL_MASK);
codePanel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
resumeStroke, "resumeAction");
codePanel.getActionMap().put("resumeAction", resumeAction);
paneResult.getActionMap().put("EOF", new AbstractAction() {
/**
*
*/
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
Document doc = paneResult.getDocument();
String s = "\u001a";
try {
doc.insertString(doc.getLength(), s, null);
} catch (BadLocationException excp) {
BrainFuckIDE.lg.severe(BrainFuckIDE.ErrorsFormatter(excp));
BrainFuckIDE.lg.severe(BrainFuckIDE.ErrorsFormatter(excp));
}
}
});
StyledDocument doc2 = (StyledDocument) paneResult.getDocument();
Style sInput = doc2.addStyle("INPUT", null);
StyleConstants.setForeground(sInput, Color.red);
Style sOutput = doc2.addStyle("OUTPUT", null);
StyleConstants.setForeground(sOutput, Color.GREEN);
Style sComment = doc.addStyle("COMMENT", null);
StyleConstants.setItalic(sComment, true);
StyleConstants.setForeground(sComment, Color.BLUE);
Style sCode = doc.addStyle("CODE", null);
StyleConstants.setForeground(sCode, Color.BLACK);
Style sError = doc.addStyle("ERROR", null);
StyleConstants.setUnderline(sError, true);
StyleConstants.setStrikeThrough(sError, true);
StyleConstants.setForeground(sError, Color.RED);
Style sBFCaret = doc.addStyle("BFCaret", null);
StyleConstants.setBackground(sBFCaret, Color.RED);
Style sBreakpoint = doc.addStyle("BREAKPOINT", null);
StyleConstants.setBackground(sBreakpoint, Color.CYAN);
Style empty = doc.addStyle("empty", null);
StyleConstants.setForeground(empty, Color.YELLOW);
Style foundStyle = doc.addStyle("found", null);
StyleConstants.setBackground(foundStyle, Color.BLUE);
high = new SyntaxHighlighter();
doc.addDocumentListener(high);
setLayout(new BorderLayout());
scrollPaneResult = new JScrollPane(paneResult);
scrollPaneResult.setPreferredSize(new Dimension(80, 80));
add(status, BorderLayout.NORTH);
add(pane, BorderLayout.CENTER);
add(scrollPaneResult, BorderLayout.SOUTH);
/*
* WordsHighlighter w = new WordsHighlighter(new String[] { "BF",
* "TODO", "FIXME", "XXX", "WARNING", "ERROR", "CRITICAL", "DEBUG",
* "FUNCTION", "PROCEDURE" }); codePanel.setHighlighter(w);
* codePanel.getDocument().addDocumentListener(w);
*/
final JTextPaneInputStream is = new JTextPaneInputStream();
final JTextPaneOutputStream os = new JTextPaneOutputStream();
paneResult.getDocument().addDocumentListener(is);
os.setPane(paneResult);
machine = new BFMachine(is, os, "", BFMachine.defaultMemorySize);
memoryPanel.setEditable(false);
memoryPanel.setPreferredSize(new Dimension(200, 200));
splitPane1 = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
editor = new JHexView();
editor.setData(new SimpleDataProvider(machine.getMemory()));
editor.setDefinitionStatus(DefinitionStatus.DEFINED);
editor.setEnabled(true);
// editor.set
splitPane1.add(pane);
splitPane1.add(editor);
splitPane1.setSize(new Dimension(800, 600));
addComponentListener(this);
// t.start();
add(splitPane1, BorderLayout.CENTER);
splitPane1.setDividerLocation(BFTab.startSplitterPosition);
}
@Override
public void caretUpdate(final CaretEvent e) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
JTextPane pane = (JTextPane) e.getSource();
BrainFuckIDE.lg.fine("Caret update" + e.getDot());
Document doc = pane.getDocument();
try {
String s = doc.getText(0, e.getDot());
TextEditorPosition pos = getLineNumer(s);
positionLabel.setText("" + pos.Line + ":" + pos.Offset);
} catch (BadLocationException excp) {
BrainFuckIDE.lg.severe(BrainFuckIDE.ErrorsFormatter(excp));
}
}
});
}
public JTextPane getArea() {
return codePanel;
}
public File getFile() {
return file;
}
public Style getFoundStyle() {
return foundStyle;
}
public SyntaxHighlighter getHigh() {
return high;
}
public String getLastCodeWorking() {
return lastCodeWorking;
}
protected TextEditorPosition getLineNumer(String s) {
String lineSeparator = System.getProperty("line.separator");
int line = 1;
int pos = -1, last = 0;
BrainFuckIDE.lg.finer("contents: " + s);
while ((pos = s.indexOf(lineSeparator, pos + lineSeparator.length())) >= 0) {
BrainFuckIDE.lg.finer("found separator: " + pos);
line++;
last = pos + lineSeparator.length();
}
return new TextEditorPosition(line, s.length() - last);
}
public BFMachine getMachine() {
return machine;
}
public JScrollPane getPane() {
return pane;
}
public JTextPane getPaneResult() {
return paneResult;
}
public JLabel getPositionLabel() {
return positionLabel;
}
public TextLineNumber getRows() {
return rows;
}
public JScrollPane getScrollPaneResult() {
return scrollPaneResult;
}
public JPanel getStatus() {
return status;
}
public JLabel getStatusLabel() {
return statusLabel;
}
public void setArea(JTextPane area) {
this.codePanel = area;
}
public void setFile(File file) {
this.file = file;
}
public void setFoundStyle(Style foundStyle) {
this.foundStyle = foundStyle;
}
public void setHigh(SyntaxHighlighter high) {
this.high = high;
}
public void setLastCodeWorking(String lastCodeWorking) {
this.lastCodeWorking = lastCodeWorking;
}
public void setMachine(BFMachine machine) {
this.machine = machine;
}
public void setPane(JScrollPane pane) {
this.pane = pane;
}
public void setPaneResult(JTextPane paneResult) {
this.paneResult = paneResult;
}
public void setPositionLabel(JLabel positionLabel) {
this.positionLabel = positionLabel;
}
public void setRows(TextLineNumber rows) {
this.rows = rows;
}
public void setScrollPaneResult(JScrollPane scrollPaneResult) {
this.scrollPaneResult = scrollPaneResult;
}
public void setStatus(JPanel status) {
this.status = status;
}
public void setStatusLabel(JLabel statusLabel) {
this.statusLabel = statusLabel;
}
@Override
public void componentResized(ComponentEvent e) {
// splitPane1.setDividerLocation(BFTab.startSplitterPosition);
}
@Override
public void componentMoved(ComponentEvent e) {
// TODO Auto-generated method stub
}
@Override
public void componentShown(ComponentEvent e) {
// TODO Auto-generated method stub
}
@Override
public void componentHidden(ComponentEvent e) {
// TODO Auto-generated method stub
}
@Override
public void actionPerformed(ActionEvent e) {
BrainFuckIDE.lg.fine("Refreshing code syntax");
StyledDocument doc = codePanel.getStyledDocument();
high.update(doc, 0, doc.getLength(), machine.isWorking());
}
@Override
public void componentAdded(ContainerEvent e) {
if (e.getChild() == this) {
BrainFuckIDE.lg.info("Component added");
t.start();
}
}
@Override
public void componentRemoved(ContainerEvent e) {
if (e.getChild() == this) {
machine.stop();
e.getContainer().removeContainerListener(this);
BrainFuckIDE.lg.info("Component removed");
t.stop();
}
}
public JHexView getEditor() {
return editor;
}
public void setEditor(JHexView editor) {
this.editor = editor;
}
public JTextPane getCodePanel() {
return codePanel;
}
public void setCodePanel(JTextPane codePanel) {
this.codePanel = codePanel;
}
public JTextPane getMemoryPanel() {
return memoryPanel;
}
public void setMemoryPanel(JTextPane memoryPanel) {
this.memoryPanel = memoryPanel;
}
public JSplitPane getSplitPane1() {
return splitPane1;
}
public void setSplitPane1(JSplitPane splitPane1) {
this.splitPane1 = splitPane1;
}
/**
* Pobiera timer dla syntaxu
*
* @return timer
*/
public Timer getT() {
return t;
}
/**
* @param t
* Timer
*/
public void setT(Timer t) {
this.t = t;
}
}

View File

@ -0,0 +1,71 @@
package tpsa;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;
import javax.swing.UIManager;
public class BrainFuckIDE {
static public Logger lg;
// protected static final long milsToWaitBeforeKill = 60000;
static private LogManager lm;
static private Level lv = Level.OFF;
/**
* @param args
*/
public static void main(String[] args) {
try {
UIManager
.setLookAndFeel("javax.swing.plaf.nimbus.NimbusLookAndFeel");
} catch (Exception excp) {
// TODO Auto-generated catch block
BrainFuckIDE.lg.severe(BrainFuckIDE.ErrorsFormatter(excp));
}
BrainFuckIDE.lm = LogManager.getLogManager();
BrainFuckIDE.lm.addLogger(Logger.getLogger("BFIDE"));
BrainFuckIDE.lg = lm.getLogger("BFIDE");
BrainFuckIDE.lg.setLevel(lv);
for (Handler h : BrainFuckIDE.lg.getHandlers())
h.setLevel(lv);
for (Handler h : Logger.getLogger("").getHandlers())
h.setLevel(lv);
BrainFuckIDE.lg.info("Brainfuck IDE is starting");
MainFrame mf = new MainFrame();
mf.setVisible(true);
}
/**
* @param excp
* Wyjątek ;-)
* @return stringi opisujące ten wyjątek w strawnej postaci
*/
public static String ErrorsFormatter(Exception excp) {
// System.setOut(null);
// System.setErr(null);
StringBuilder sb = new StringBuilder();
String newLine = System.getProperty("line.separator");
sb.append("Exception catched: " + excp.getLocalizedMessage() + newLine);
StackTraceElement[] stack = excp.getStackTrace();
for (int i = 0; i < stack.length; i++) {
sb.append("StackElement(" + i + ") : " + stack[i].getClassName()
+ ", " + stack[i].getFileName() + ":"
+ stack[i].getLineNumber() + ", "
+ stack[i].getMethodName() + newLine);
}
return sb.toString();
}
}

View File

@ -0,0 +1,183 @@
/*
* Copyright (c) 1995, 2008, Oracle and/or its affiliates. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of Oracle or the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package tpsa;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.AbstractButton;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import javax.swing.plaf.basic.BasicButtonUI;
/**
* Component to be used as tabComponent;
* Contains a JLabel to show the text and
* a JButton to close the tab it belongs to
*/
public class ButtonTabComponent extends JPanel {
private class TabButton extends JButton implements ActionListener {
/**
*
*/
private static final long serialVersionUID = -7852819509277461601L;
public TabButton() {
int size = 17;
setPreferredSize(new Dimension(size, size));
setToolTipText("close this tab");
//Make the button looks the same for all Laf's
setUI(new BasicButtonUI());
//Make it transparent
setContentAreaFilled(false);
//No need to be focusable
setFocusable(false);
setBorder(BorderFactory.createEtchedBorder());
setBorderPainted(false);
//Making nice rollover effect
//we use the same listener for all buttons
addMouseListener(buttonMouseListener);
setRolloverEnabled(true);
//Close the proper tab by clicking the button
addActionListener(this);
}
@Override
public void actionPerformed(ActionEvent e) {
int i = pane.indexOfTabComponent(ButtonTabComponent.this);
if (i != -1) {
pane.remove(i);
}
}
//paint the cross
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g.create();
//shift the image for pressed buttons
if (getModel().isPressed()) {
g2.translate(1, 1);
}
g2.setStroke(new BasicStroke(2));
g2.setColor(Color.BLACK);
if (getModel().isRollover()) {
g2.setColor(Color.MAGENTA);
}
int delta = 6;
g2.drawLine(delta, delta, getWidth() - delta - 1, getHeight() - delta - 1);
g2.drawLine(getWidth() - delta - 1, delta, delta, getHeight() - delta - 1);
g2.dispose();
}
//we don't want to update UI for this button
@Override
public void updateUI() {
}
}
private final static MouseListener buttonMouseListener = new MouseAdapter() {
@Override
public void mouseEntered(MouseEvent e) {
Component component = e.getComponent();
if (component instanceof AbstractButton) {
AbstractButton button = (AbstractButton) component;
button.setBorderPainted(true);
}
}
@Override
public void mouseExited(MouseEvent e) {
Component component = e.getComponent();
if (component instanceof AbstractButton) {
AbstractButton button = (AbstractButton) component;
button.setBorderPainted(false);
}
}
};
/**
*
*/
private static final long serialVersionUID = -2329475802746012869L;
private final JTabbedPane pane;
public ButtonTabComponent(final JTabbedPane pane) {
//unset default FlowLayout' gaps
super(new FlowLayout(FlowLayout.LEFT, 0, 0));
if (pane == null) {
throw new NullPointerException("TabbedPane is null");
}
this.pane = pane;
setOpaque(false);
//make JLabel read titles from JTabbedPane
JLabel label = new JLabel() {
/**
*
*/
private static final long serialVersionUID = 6069043534335090073L;
@Override
public String getText() {
int i = pane.indexOfTabComponent(ButtonTabComponent.this);
if (i != -1) {
return pane.getTitleAt(i);
}
return null;
}
};
add(label);
//add more space between the label and the button
label.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 5));
//tab button
JButton button = new TabButton();
add(button);
//add more space to the top of the component
setBorder(BorderFactory.createEmptyBorder(2, 0, 0, 0));
}
}

View File

@ -0,0 +1,970 @@
package tpsa;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Event;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Arrays;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JViewport;
import javax.swing.KeyStroke;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.UndoableEditEvent;
import javax.swing.event.UndoableEditListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultEditorKit;
import javax.swing.text.Document;
import javax.swing.text.Style;
import javax.swing.text.StyledDocument;
import javax.swing.undo.UndoManager;
import tpsa.dialogs.AboutProgramDialog;
import tpsa.dialogs.FindDialog;
import tpsa.dialogs.HelpDialog;
import tpsa.dialogs.MemorySizeDialog;
import tpsa.dialogs.ReplaceDialog;
import tpsa.dialogs.filters.BFFileFilter;
import tpsa.highlighters.SyntaxHighlighter;
// DONE liczenie klamerek (bilans)
// DONE uruchomienie ostatniej poprawnej wersji BrainFucka
// DONE zróżnicowanie podświetlenia (prawie zrobione - działa, ale więcej)
// DONE ustalenie skończonej pamięci
// DONE wyświetlanie ładnych wyjątków
// DONE wyróżnienie komentarzy
// DONE debugowanie
// DONE wyświetlanie stanu taśmy
// DONE scenariusze testowe - przykłady działające, i nie działające, poprawnie obsłużyć
// DONE krótki opis BrainFucka
/**
* Klasa głowna edytora, odpowiedzialna za GUI edytora
*
* @author tpsa
*/
public class MainFrame extends JFrame implements CaretListener,
UndoableEditListener, ChangeListener {
static int lastBFPositionShowing = -1;
/**
*
*/
private static final long serialVersionUID = -4950512615304159756L;
/**
*
*/
/**
* JTextPane w którym umieszczamy kod BF
*/
private AbstractAction findAction;
private FindDialog findDialog;
private Action newFileAction;
private AbstractAction openAction;
private AbstractAction pauseAction;
private JMenuItem pauseMachine;
/**
* Label, który pokazuje aktualną pozycję w tekście
*/
private JMenuItem redo;
private RedoAction redoAction;
private AbstractAction replaceAction;
private ReplaceDialog replaceDialog;
private AbstractAction resumeAction;
private JMenuItem resumeMachine;
private AbstractAction runAction;
@SuppressWarnings("unused")
private AbstractAction runLastWorkingCode = null;
private JMenuItem runWithoutDebug;
private AbstractAction saveAction;
private AbstractAction saveKnownAction;
private AbstractAction showLinesAction;
private Action showStatusAction;
private JMenuItem showStatusLabel;
private Action stepAction;
private JMenuItem stepMachine;
private AbstractAction stopAction;
private JMenuItem stopMachine;
private JTabbedPane tabs = new JTabbedPane();
private JMenuItem undo;
private UndoAction undoAction;
private UndoManager undoManager = new UndoManager();
private AbstractAction runActionWithDebug;
private AbstractAction showMemoryPanelAction;
protected byte[] lastWorkingCode;
/**
* Tworzy instancję klasy MainFrame
*/
public MainFrame() {
super("Brainfuck IDE");
BrainFuckIDE.lg.info("Creating main frame of Brainfuck IDE");
tabs.addChangeListener(this);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setPreferredSize(new Dimension(800, 600));
replaceDialog = new ReplaceDialog(this);
findDialog = new FindDialog(this);
BrainFuckIDE.lg.fine("Setting actions");
setActions();
JMenuBar menuBar = getMainFrameMenuBar();
setJMenuBar(menuBar);
// tab.getArea().addMouseListener(this);
// tab.getArea().addMouseMotionListener(this);
undoAction = new UndoAction(undoManager, undo, null);
redoAction = new RedoAction(undoManager, redo, undoAction);
undo.setAction(undoAction);
undo.setText("Undo");
redo.setAction(redoAction);
redo.setText("Redo");
undoAction.setRedoAction(redoAction);
BFTab tab1 = new BFTab(this, pauseAction, resumeAction, stepAction,
stopAction, runAction, undoAction, redoAction);
tabs.addContainerListener(tab1);
tabs.add(tab1);
int idx = tabs.indexOfComponent(tab1);
tabs.setTitleAt(idx, "Untitled");
tabs.setTabComponentAt(idx, new ButtonTabComponent(tabs));
add(tabs, BorderLayout.CENTER);
// add(status, BorderLayout.SOUTH);
BrainFuckIDE.lg.info("Adding document listener");
pack();
}
@Override
public void caretUpdate(final CaretEvent e) {
}
/**
* Generuje menu programu
*
* @return Zwraca instancję JMenuBar (menu), dla programu
*/
private JMenuBar getMainFrameMenuBar() {
JMenuBar menuBar = new JMenuBar();
JMenu file = new JMenu("File");
JMenu edit = new JMenu("Edit");
JMenu view = new JMenu("View");
JMenu run = new JMenu("Run");
JMenu help = new JMenu("Help");
menuBar.add(file);
menuBar.add(edit);
menuBar.add(view);
menuBar.add(run);
menuBar.add(help);
help.add(new JMenuItem(new AbstractAction("About program") {
/**
*
*/
private static final long serialVersionUID = 5839320025951821584L;
@Override
public void actionPerformed(ActionEvent e) {
BrainFuckIDE.lg.info("About program action performed");
Container c = tabs.getTopLevelAncestor();
AboutProgramDialog dialog = new AboutProgramDialog((JFrame) c);
dialog.setLocationRelativeTo(c);
dialog.setLocation(c.getWidth() / 2, c.getHeight() / 2);
dialog.showDialog();
}
}));
help.add(new JMenuItem(new AbstractAction("Tutorial") {
/**
*
*/
private static final long serialVersionUID = -6114772901713788593L;
@Override
public void actionPerformed(ActionEvent e) {
BrainFuckIDE.lg.info("Help action performed");
Container c = tabs.getTopLevelAncestor();
HelpDialog dialog = new HelpDialog((JFrame) c);
dialog.setLocationRelativeTo(c);
dialog.setLocation(c.getWidth() / 2, c.getHeight() / 2);
dialog.showDialog();
}
}));
JMenuItem newFile = new JMenuItem("New file");
newFile.setAction(newFileAction);
JMenuItem open = new JMenuItem("Open");
open.setAction(openAction);
JMenuItem save = new JMenuItem("Save");
JMenuItem saveAs = new JMenuItem("Save as");
saveAs.setAction(saveAction);
JMenuItem quit = new JMenuItem("Quit");
quit.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.exit(0);
}
});
file.add(newFile);
file.add(open);
save.setAction(saveKnownAction);
file.add(save);
file.add(saveAs);
file.add(quit);
undo = new JMenuItem("Undo");
redo = new JMenuItem("Redo");
JMenuItem cut = new JMenuItem("Cut");
cut.addActionListener(new DefaultEditorKit.CutAction());
// cut.setMnemonic(KeyEvent.VK_X);
JMenuItem copy = new JMenuItem("Copy");
copy.addActionListener(new DefaultEditorKit.CopyAction());
// copy.setMnemonic(KeyEvent.VK_C);
JMenuItem paste = new JMenuItem("Paste");
paste.addActionListener(new DefaultEditorKit.PasteAction());
// paste.setMnemonic(KeyEvent.VK_P);
JMenuItem find = new JMenuItem("Find");
find.setAction(findAction);
KeyStroke findStroke = KeyStroke.getKeyStroke(KeyEvent.VK_F,
Event.CTRL_MASK);
find.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(findStroke,
"find");
find.getActionMap().put("find", findAction);
JMenuItem replace = new JMenuItem("Replace");
replace.setAction(replaceAction);
KeyStroke replaceStroke = KeyStroke.getKeyStroke(KeyEvent.VK_R,
Event.CTRL_MASK);
find.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(replaceStroke,
"replace");
find.getActionMap().put("replace", replaceAction);
KeyStroke newFileStroke = KeyStroke.getKeyStroke(KeyEvent.VK_N,
Event.CTRL_MASK);
newFile.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
newFileStroke, "newFile");
newFile.getActionMap().put("newFile", newFileAction);
KeyStroke saveFileStroke = KeyStroke.getKeyStroke(KeyEvent.VK_S,
Event.CTRL_MASK);
save.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(saveFileStroke,
"saveFile");
save.getActionMap().put("saveFile", saveKnownAction);
edit.add(undo);
edit.add(redo);
edit.addSeparator();
edit.add(cut);
edit.add(copy);
edit.add(paste);
edit.addSeparator();
edit.add(find);
edit.add(replace);
edit.addSeparator();
edit.add(new JMenuItem(new AbstractAction("Memory preferences") {
/**
*
*/
private static final long serialVersionUID = 6096201895928552672L;
@Override
public void actionPerformed(ActionEvent e) {
BrainFuckIDE.lg.info("Memory preferences action performed");
MainFrame frame = (MainFrame) tabs.getTopLevelAncestor();
MemorySizeDialog dialog = new MemorySizeDialog(frame);
dialog.setSizeMemory(BFMachine.defaultMemorySize);
if (!dialog.showDialog())
return;
BFMachine.defaultMemorySize = dialog.getSizeMemory();
}
}));
JMenuItem showLineNumbers = new JMenuItem("Show line numbers");
showStatusLabel = new JCheckBoxMenuItem("Show status label");
view.add(showLineNumbers);
showLineNumbers.setAction(showLinesAction);
view.add(showMemoryPanelAction);
view.add(showStatusLabel);
showStatusLabel.setAction(showStatusAction);
runWithoutDebug = new JMenuItem("Run without debug");
pauseMachine = new JMenuItem("Pause machine");
resumeMachine = new JMenuItem("Resume machine");
stopMachine = new JMenuItem("Stop machine");
stepMachine = new JMenuItem("Step machine");
runWithoutDebug.setAction(runAction);
pauseMachine.setAction(pauseAction);
resumeMachine.setAction(resumeAction);
stopMachine.setAction(stopAction);
stepMachine.setAction(stepAction);
run.add(runActionWithDebug);
run.add(runWithoutDebug);
// run.add(runLastWorkingCode);
run.add(pauseMachine);
run.add(resumeMachine);
run.add(stopMachine);
run.add(stepAction);
return menuBar;
}
/**
* Ustawia akcje np. find, replace Każda akcja ma reakcję
*/
private void setActions() {
showMemoryPanelAction = new AbstractAction("Show memory panel") {
/**
*
*/
private static final long serialVersionUID = 2012577125654615111L;
@Override
public void actionPerformed(ActionEvent e) {
BrainFuckIDE.lg.info("Show memory action performed");
BFTab tab = (BFTab) tabs.getSelectedComponent();
if (tab == null)
return;
JSplitPane pane = tab.getSplitPane1();
ArrayList<Component> components = new ArrayList<Component>(
Arrays.asList(pane.getComponents()));
BrainFuckIDE.lg.finest("Got components");
boolean contains = false;
for (Component c : components)
if (c == tab.getEditor()) {
contains = true;
break;
}
System.err.println(components.size());
if (contains) {
BrainFuckIDE.lg.info("Removing memory panel");
pane.remove(tab.getEditor());
} else {
BrainFuckIDE.lg.info("Adding memory panel");
pane.add(tab.getEditor(), 1);
pane.setDividerLocation(BFTab.startSplitterPosition);
}
}
};
newFileAction = new AbstractAction("New file") {
/**
*
*/
private static final long serialVersionUID = 5483399276425267838L;
@Override
public void actionPerformed(ActionEvent e) {
MainFrame frame = (MainFrame) tabs.getTopLevelAncestor();
BFTab tab = new BFTab(frame, pauseAction, resumeAction,
stepAction, stopAction, runAction, undoAction,
redoAction);
tabs.addContainerListener(tab);
tabs.add(tab);
int idx = tabs.indexOfComponent(tab);
tabs.setTitleAt(idx, "Untitled");
tabs.setTabComponentAt(idx, new ButtonTabComponent(tabs));
}
};
runAction = new AbstractAction("RunWithoutDebug") {
/**
*
*/
private static final long serialVersionUID = 5839320025951821584L;
@Override
public void actionPerformed(ActionEvent e) {
BrainFuckIDE.lg.info("Run action executed");
final BFTab tab = (BFTab) tabs.getSelectedComponent();
Document doc = tab.getPaneResult().getDocument();
final BFMachine machine = tab.getMachine();
final Thread t = prepareMachine(tab, doc, machine);
if (t == null)
return;
machine.setStepping(false);
t.start();
}
};
runActionWithDebug = new AbstractAction("Run with debug") {
/**
*
*/
private static final long serialVersionUID = 6096201895928552672L;
@Override
public void actionPerformed(ActionEvent e) {
BrainFuckIDE.lg.info("Run action executed");
final BFTab tab = (BFTab) tabs.getSelectedComponent();
Document doc = tab.getPaneResult().getDocument();
final BFMachine machine = tab.getMachine();
final Thread t = prepareMachine(tab, doc, machine);
if (t == null)
return;
machine.setStepping(true);
t.start();
}
};
showStatusAction = new AbstractAction("ShowStatusLabel") {
/**
*
*/
private static final long serialVersionUID = 2012577125654615111L;
@Override
public void actionPerformed(ActionEvent e) {
// BrainFuckIDE.lg.finest("showStatusLabel.isSelected());
BFTab tab = (BFTab) tabs.getSelectedComponent();
Component[] comp = tab.getComponents();
ArrayList<Component> l = new ArrayList<Component>(
Arrays.asList(comp));
boolean contains = l.contains(tab.getStatus());
if (!contains) {
tab.add(tab.getStatus(), BorderLayout.NORTH);
} else
tab.remove(tab.getStatus());
}
};
showLinesAction = new AbstractAction("ShowLines") {
/**
*
*/
private static final long serialVersionUID = 2012577125654615111L;
@Override
public void actionPerformed(ActionEvent e) {
BFTab tab = (BFTab) tabs.getSelectedComponent();
JScrollPane pane = tab.getPane();
JViewport port = pane.getRowHeader();
if (port == null)
pane.setRowHeaderView(tab.getRows());
else
pane.setRowHeader(null);
}
};
runLastWorkingCode = new AbstractAction("Run last working code") {
/**
*
*/
private static final long serialVersionUID = -1126695922005433078L;
@Override
public void actionPerformed(ActionEvent e) {
BFTab tab = (BFTab) tabs.getSelectedComponent();
if (tab == null)
return;
StyledDocument doc = tab.getArea().getStyledDocument();
BFMachine machine = tab.getMachine();
if (machine.isWorking()) {
JOptionPane.showMessageDialog(tabs.getTopLevelAncestor(),
"Machine is working");
return;
}
Thread t = prepareMachine(tab, doc, machine);
if (lastWorkingCode == null) {
JOptionPane.showMessageDialog(tabs.getTopLevelAncestor(),
"no working code");
tab.getStatusLabel().setText("Ready");
return;
}
machine.setCode(lastWorkingCode);
t.start();
}
};
pauseAction = new AbstractAction("Pause machine") {
/**
*
*/
private static final long serialVersionUID = 6096201895928552672L;
@Override
public void actionPerformed(ActionEvent e) {
BrainFuckIDE.lg.info("Pause action performed");
BFTab tab = (BFTab) tabs.getSelectedComponent();
BFMachine machine = tab.getMachine();
if (!machine.isWorking()) {
JOptionPane.showMessageDialog(null, "No maching running");
return;
}
machine.pause();
System.err.println("Paused");
tab.getStatusLabel().setText("Machine paused");
}
};
resumeAction = new AbstractAction("Resume machine") {
/**
*
*/
private static final long serialVersionUID = 2012577125654615111L;
@Override
public void actionPerformed(ActionEvent e) {
BrainFuckIDE.lg.info("Resume action performed");
BFTab tab = (BFTab) tabs.getSelectedComponent();
BFMachine machine = tab.getMachine();
if (!machine.isWorking()) {
JOptionPane.showMessageDialog(null, "No maching running");
return;
}
machine.setStepping(false);
machine.resume();
tab.getStatusLabel().setText("Running...");
}
};
stopAction = new AbstractAction("Stop machine") {
/**
*
*/
private static final long serialVersionUID = -2599448412251509415L;
@Override
public void actionPerformed(ActionEvent e) {
BrainFuckIDE.lg.info("Stop action performed");
BFTab tab = (BFTab) tabs.getSelectedComponent();
BFMachine machine = tab.getMachine();
if (!machine.isWorking()) {
JOptionPane.showMessageDialog(null, "No maching running");
return;
}
machine.stop();
tab.getStatusLabel().setText("Machine stopped - ready...");
tab.getArea().setEditable(true);
}
};
stepAction = new AbstractAction("StepAction") {
/**
*
*/
private static final long serialVersionUID = 5880656879834148166L;
@Override
public void actionPerformed(ActionEvent e) {
BrainFuckIDE.lg.info("Step action performed");
BFTab tab = (BFTab) tabs.getSelectedComponent();
BFMachine machine = tab.getMachine();
if (!machine.isWorking()) {
JOptionPane.showMessageDialog(getParent(),
"Machine is not working");
return;
}
machine.resume();
machine.setStepping(true);
}
};
openAction = new AbstractAction("open") {
/**
*
*/
private static final long serialVersionUID = 4575053887363265531L;
@Override
public void actionPerformed(ActionEvent e) {
try {
JFileChooser chooser = new JFileChooser(".");
chooser.setFileFilter(new BFFileFilter());
if (chooser.showDialog(null, "Otwórz") == JFileChooser.APPROVE_OPTION) {
File f = chooser.getSelectedFile();
BufferedReader r = new BufferedReader(
new InputStreamReader(new FileInputStream(f)));
StringBuilder sb = new StringBuilder();
char[] buffer = new char[0x1000];
while (r.read(buffer) >= 0)
sb.append(buffer);
r.close();
MainFrame frame = (MainFrame) tabs
.getTopLevelAncestor();
BFTab tab = new BFTab(frame, pauseAction, resumeAction,
stepAction, stopAction, runAction, undoAction,
redoAction);
tabs.add(tab);
int idx = tabs.indexOfComponent(tab);
tab.setFile(f);
tabs.setTitleAt(idx, f.getName());
tabs.setTabComponentAt(idx,
new ButtonTabComponent(tabs));
tabs.setSelectedIndex(idx);
tab.getArea().setText(sb.toString());
tab.getArea().setCaretPosition(0);
}
} catch (IOException excp) {
BrainFuckIDE.lg.severe(BrainFuckIDE.ErrorsFormatter(excp));
}
}
};
saveKnownAction = new AbstractAction("Save") {
/**
*
*/
private static final long serialVersionUID = -985908773082887131L;
@Override
public void actionPerformed(ActionEvent e) {
BFTab tab = (BFTab) tabs.getSelectedComponent();
if (tab == null)
return;
File f = tab.getFile();
if (f == null) {
saveAction.actionPerformed(e);
f = tab.getFile();
if (f == null) {
JOptionPane
.showMessageDialog(tabs.getTopLevelAncestor(),
"Coś się schrzaniło\nW zasadzie nie wiem, nawet co");
return;
}
}
OutputStreamWriter writer;
try {
writer = new OutputStreamWriter(new FileOutputStream(f));
writer.write(tab.getArea().getText());
writer.close();
} catch (FileNotFoundException excp) {
BrainFuckIDE.lg.severe(BrainFuckIDE.ErrorsFormatter(excp));
} catch (IOException excp) {
BrainFuckIDE.lg.severe(BrainFuckIDE.ErrorsFormatter(excp));
}
}
};
saveAction = new AbstractAction("Save as") {
/**
*
*/
private static final long serialVersionUID = 4575053887363265531L;
@Override
public void actionPerformed(ActionEvent e) {
try {
BFTab tab = (BFTab) tabs.getSelectedComponent();
JFileChooser chooser = new JFileChooser(".");
chooser.setFileFilter(new BFFileFilter());
if (chooser.showDialog(null, "Zapisz") == JFileChooser.APPROVE_OPTION) {
File f = chooser.getSelectedFile();
tabs.setTitleAt(tabs.getSelectedIndex(), f.getName());
tab.setFile(f);
BufferedWriter r = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(f)));
r.write(tab.getArea().getText());
r.close();
}
} catch (IOException excp) {
BrainFuckIDE.lg.severe(BrainFuckIDE.ErrorsFormatter(excp));
}
}
};
findAction = new AbstractAction("find") {
/**
*
*/
private static final long serialVersionUID = 8670201600032148368L;
@Override
public void actionPerformed(ActionEvent e) {
BrainFuckIDE.lg.info("Action find performed");
findDialog.setVisible(true);
}
};
replaceAction = new AbstractAction("replace") {
/**
*
*/
private static final long serialVersionUID = 4575053887363265531L;
@Override
public void actionPerformed(ActionEvent e) {
BrainFuckIDE.lg.info("Action action performed");
replaceDialog.setVisible(true);
}
};
}
private void showBFProgress() {
try {
BrainFuckIDE.lg.info("Show BF progress");
BFTab tab = (BFTab) tabs.getSelectedComponent();
BFMachine machine = tab.getMachine();
int pos = machine.getPosition();
tab.getStatusLabel().setText(
"Stepping pos: " + pos + "("
+ (char) tab.getMachine().getCode()[pos]
+ "), memory pos : "
+ tab.getMachine().getMemoryPointer());
tab.getEditor().setCurrentOffset(
tab.getMachine().getMemoryPointer());
StyledDocument doc = (StyledDocument) tab.getArea().getDocument();
Style caretStyle = doc.getStyle("BFCaret");
Style codeStyle = doc.getStyle("CODE");
Style commentStyle = doc.getStyle("COMMENT");
if (lastBFPositionShowing >= 0) {
String s = doc.getText(pos, 1);
Character c = s.charAt(0);
if (c == '[' || c == ']' || c == '\n' || c == '\r'
|| SyntaxHighlighter.BFcharacterList.contains(c))
doc.setCharacterAttributes(lastBFPositionShowing, 1,
codeStyle, true);
else
doc.setCharacterAttributes(lastBFPositionShowing, 1,
commentStyle, true);
}
doc.setCharacterAttributes(pos, 1, caretStyle, true);
lastBFPositionShowing = pos;
} catch (BadLocationException excp) {
BrainFuckIDE.lg.warning(BrainFuckIDE.ErrorsFormatter(excp));
}
}
@Override
public void undoableEditHappened(UndoableEditEvent e) {
BrainFuckIDE.lg.info("Undoable edit happend: "
+ e.getEdit().getPresentationName());
// FIXME jak na razie, działa, ale nie wiem dlaczego, jeżeli coś się
// sypie z UndoManager tu sprawdzać
if (e.getEdit().getPresentationName().equals("style change") == false)
undoManager.addEdit(e.getEdit());
undoAction.update();
redoAction.update();
}
@Override
public void stateChanged(ChangeEvent e) {
BFTab tab = (BFTab) tabs.getSelectedComponent();
if (tab == null)
return;
if (tab.getComponentCount() <= 0)
return;
ArrayList<Component> components = new ArrayList<Component>(
Arrays.asList(tab.getComponents()));
showStatusLabel.setSelected(components.contains(tab.getStatus()));
}
/**
* Szykuje maszynę, na nowe zawody (reset)
*
* @param tab
* zakładka
* @param doc
* dokument
* @param machine
* maszyna
* @return
*/
private Thread prepareMachine(final BFTab tab, Document doc,
final BFMachine machine) {
if (machine.isWorking()) {
JOptionPane.showMessageDialog(null,
"Machine is working currently!!!");
return null;
}
tab.getArea().setEditable(false);
BrainFuckIDE.lg
.info("Adding document listener to the input stream of console");
try {
doc.remove(0, doc.getLength());
} catch (BadLocationException excp) {
BrainFuckIDE.lg.severe("Exception: ");
}
BrainFuckIDE.lg.fine("Creating BF machine");
machine.setCode(tab.getArea().getText());
machine.setFinishHandler(new Runnable() {
@Override
public void run() {
BFTab tab = (BFTab) tabs.getSelectedComponent();
String s = machine.getError();
if (s != null && !s.equals(""))
JOptionPane.showMessageDialog(tabs.getTopLevelAncestor(),
"Whoops: BF machine" + " complaints: " + s,
"BF error", JOptionPane.ERROR_MESSAGE);
else
lastWorkingCode = tab.getMachine().getCode();
tab.getEditor().repaint();
BrainFuckIDE.lg.info("Memory of BF machine is: "
+ tab.getMachine().getMemory().length);
tab.getArea().setEditable(true);
tab.getStatusLabel().setText("Ready");
}
});
machine.setBreakpointHandler(new Runnable() {
@Override
public void run() {
showBFProgress();
tab.getStatusLabel().setText(
tab.getStatusLabel().getText()
+ ". Breakpoint reached...");
}
});
machine.setStepHandler(new Runnable() {
@Override
public void run() {
showBFProgress();
}
});
final Thread t = new Thread(machine);
tab.getMachine().reset();
tab.getStatusLabel().setText("Running...");
return t;
}
public JTabbedPane getTabs() {
return tabs;
}
public void setTabs(JTabbedPane tabs) {
this.tabs = tabs;
}
}

View File

@ -0,0 +1,73 @@
package tpsa;
import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JMenuItem;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.UndoManager;
/**
* Klasa umożliwia działanie ReDo
*
* @author Duga Eye
*/
public class RedoAction extends AbstractAction {
/**
*
*/
private static final long serialVersionUID = -2467991386706847458L;
private JMenuItem redoButton;
private UndoAction undoAction;
private UndoManager undoManager;
/**
* @param manager
* @param button
* @param action
*/
public RedoAction(UndoManager manager, JMenuItem button, UndoAction action) {
super("Redo");
setEnabled(false);
undoManager = manager;
redoButton = button;
undoAction = action;
}
@Override
public void actionPerformed(ActionEvent e) {
try {
undoManager.redo();
} catch (CannotRedoException ex) {
}
update();
undoAction.update();
}
/**
* @param action
* Akcja analogiczna do ReDo Undo
*/
public void setUndoAction(UndoAction action) {
undoAction = action;
}
/**
* Uaktualnia historię edycji
*/
protected void update() {
BrainFuckIDE.lg.fine("Updating state of undo/redo action");
if (undoManager.canRedo()) {
setEnabled(true);
redoButton.setEnabled(true);
putValue(Action.NAME, undoManager.getRedoPresentationName());
} else {
setEnabled(false);
redoButton.setEnabled(false);
putValue(Action.NAME, "Redo");
}
}
}

View File

@ -0,0 +1,33 @@
package tpsa;
/**
* Klasa pozycji w dokumencie
*
* @author Duga Eye
*
*/
public class TextEditorPosition {
/**
* Linia w dokumencie
*/
public int Line;
/**
* Offset w linii dokumentu
*/
public int Offset;
/**
*
*/
public TextEditorPosition()
{
Line = 0;
Offset = 0;
}
public TextEditorPosition(int line, int offset) {
this.Line = line;
this.Offset = offset;
}
}

View File

@ -0,0 +1,484 @@
package tpsa;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.HashMap;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.border.Border;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.border.MatteBorder;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.Element;
import javax.swing.text.JTextComponent;
import javax.swing.text.StyleConstants;
import javax.swing.text.Utilities;
/**
* This class will display line numbers for a related text component. The text
* component must use the same line height for each line. TextLineNumber
* supports wrapped lines and will highlight the line number of the current
* line in the text component.
*
* This class was designed to be used as a component added to the row header
* of a JScrollPane.
*/
public class TextLineNumber extends JPanel
implements CaretListener, DocumentListener, PropertyChangeListener
{
public final static float CENTER = 0.5f;
private final static int HEIGHT = Integer.MAX_VALUE - 1000000;
public final static float LEFT = 0.0f;
private final static Border OUTER = new MatteBorder(0, 0, 0, 2, Color.GRAY);
public final static float RIGHT = 1.0f;
/**
*
*/
private static final long serialVersionUID = 3696719794891436122L;
// Text component this TextTextLineNumber component is in sync with
private int borderGap;
// Properties that can be changed
private JTextComponent component;
private Color currentLineForeground;
private float digitAlignment;
private HashMap<String, FontMetrics> fonts;
private int lastDigits;
// Keep history information to reduce the number of times the component
// needs to be repainted
private int lastHeight;
private int lastLine;
private int minimumDisplayDigits;
private boolean updateFont;
/**
* Create a line number component for a text component. This minimum
* display width will be based on 3 digits.
*
* @param component the related text component
*/
public TextLineNumber(JTextComponent component)
{
this(component, 3);
}
/**
* Create a line number component for a text component.
*
* @param component the related text component
* @param minimumDisplayDigits the number of digits used to calculate
* the minimum width of the component
*/
public TextLineNumber(JTextComponent component, int minimumDisplayDigits)
{
this.component = component;
setFont( component.getFont() );
setBorderGap( 5 );
setCurrentLineForeground( Color.RED );
setDigitAlignment( RIGHT );
setMinimumDisplayDigits( minimumDisplayDigits );
component.getDocument().addDocumentListener(this);
component.addCaretListener( this );
component.addPropertyChangeListener("font", this);
}
//
// Implement CaretListener interface
//
@Override
public void caretUpdate(CaretEvent e)
{
// Get the line the caret is positioned on
int caretPosition = component.getCaretPosition();
Element root = component.getDocument().getDefaultRootElement();
int currentLine = root.getElementIndex( caretPosition );
// Need to repaint so the correct line number can be highlighted
if (lastLine != currentLine)
{
repaint();
lastLine = currentLine;
}
}
//
// Implement DocumentListener interface
//
@Override
public void changedUpdate(DocumentEvent e)
{
documentChanged();
}
/*
* A document change may affect the number of displayed lines of text.
* Therefore the lines numbers will also change.
*/
private void documentChanged()
{
// Preferred size of the component has not been updated at the time
// the DocumentEvent is fired
SwingUtilities.invokeLater(new Runnable()
{
@Override
public void run()
{
int preferredHeight = component.getPreferredSize().height;
// Document change has caused a change in the number of lines.
// Repaint to reflect the new line numbers
if (lastHeight != preferredHeight)
{
setPreferredWidth();
repaint();
lastHeight = preferredHeight;
}
}
});
}
/**
* Gets the border gap
*
* @return the border gap in pixels
*/
public int getBorderGap()
{
return borderGap;
}
/**
* Gets the current line rendering Color
*
* @return the Color used to render the current line number
*/
public Color getCurrentLineForeground()
{
return currentLineForeground == null ? getForeground() : currentLineForeground;
}
/**
* Gets the digit alignment
*
* @return the alignment of the painted digits
*/
public float getDigitAlignment()
{
return digitAlignment;
}
/**
* Gets the minimum display digits
*
* @return the minimum display digits
*/
public int getMinimumDisplayDigits()
{
return minimumDisplayDigits;
}
/*
* Determine the X offset to properly align the line number when drawn
*/
private int getOffsetX(int availableWidth, int stringWidth)
{
return (int)((availableWidth - stringWidth) * digitAlignment);
}
/*
* Determine the Y offset for the current row
*/
private int getOffsetY(int rowStartOffset, FontMetrics fontMetrics)
throws BadLocationException
{
// Get the bounding rectangle of the row
Rectangle r = component.modelToView( rowStartOffset );
int lineHeight = fontMetrics.getHeight();
int y = r.y + r.height;
int descent = 0;
// The text needs to be positioned above the bottom of the bounding
// rectangle based on the descent of the font(s) contained on the row.
if (r.height == lineHeight) // default font is being used
{
descent = fontMetrics.getDescent();
}
else // We need to check all the attributes for font changes
{
if (fonts == null)
fonts = new HashMap<String, FontMetrics>();
Element root = component.getDocument().getDefaultRootElement();
int index = root.getElementIndex( rowStartOffset );
Element line = root.getElement( index );
for (int i = 0; i < line.getElementCount(); i++)
{
Element child = line.getElement(i);
AttributeSet as = child.getAttributes();
String fontFamily = (String)as.getAttribute(StyleConstants.FontFamily);
Integer fontSize = (Integer)as.getAttribute(StyleConstants.FontSize);
String key = fontFamily + fontSize;
FontMetrics fm = fonts.get( key );
if (fm == null)
{
Font font = new Font(fontFamily, Font.PLAIN, fontSize);
fm = component.getFontMetrics( font );
fonts.put(key, fm);
}
descent = Math.max(descent, fm.getDescent());
}
}
return y - descent;
}
/*
* Get the line number to be drawn. The empty string will be returned
* when a line of text has wrapped.
*/
protected String getTextLineNumber(int rowStartOffset)
{
Element root = component.getDocument().getDefaultRootElement();
int index = root.getElementIndex( rowStartOffset );
Element line = root.getElement( index );
if (line.getStartOffset() == rowStartOffset)
return String.valueOf(index + 1);
else
return "";
}
/**
* Gets the update font property
*
* @return the update font property
*/
public boolean getUpdateFont()
{
return updateFont;
}
@Override
public void insertUpdate(DocumentEvent e)
{
documentChanged();
}
/*
* We need to know if the caret is currently positioned on the line we
* are about to paint so the line number can be highlighted.
*/
private boolean isCurrentLine(int rowStartOffset)
{
int caretPosition = component.getCaretPosition();
Element root = component.getDocument().getDefaultRootElement();
if (root.getElementIndex( rowStartOffset ) == root.getElementIndex(caretPosition))
return true;
else
return false;
}
/**
* Draw the line numbers
*/
@Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
// Determine the width of the space available to draw the line number
FontMetrics fontMetrics = component.getFontMetrics( component.getFont() );
Insets insets = getInsets();
int availableWidth = getSize().width - insets.left - insets.right;
// Determine the rows to draw within the clipped bounds.
Rectangle clip = g.getClipBounds();
int rowStartOffset = component.viewToModel( new Point(0, clip.y) );
int endOffset = component.viewToModel( new Point(0, clip.y + clip.height) );
while (rowStartOffset <= endOffset)
{
try
{
if (isCurrentLine(rowStartOffset))
g.setColor( getCurrentLineForeground() );
else
g.setColor( getForeground() );
// Get the line number as a string and then determine the
// "X" and "Y" offsets for drawing the string.
String lineNumber = getTextLineNumber(rowStartOffset);
int stringWidth = fontMetrics.stringWidth( lineNumber );
int x = getOffsetX(availableWidth, stringWidth) + insets.left;
int y = getOffsetY(rowStartOffset, fontMetrics);
g.drawString(lineNumber, x, y);
// Move to the next row
rowStartOffset = Utilities.getRowEnd(component, rowStartOffset) + 1;
}
catch(Exception e) {}
}
}
//
// Implement PropertyChangeListener interface
//
@Override
public void propertyChange(PropertyChangeEvent evt)
{
if (evt.getNewValue() instanceof Font)
{
if (updateFont)
{
Font newFont = (Font) evt.getNewValue();
setFont(newFont);
lastDigits = 0;
setPreferredWidth();
}
else
{
repaint();
}
}
}
@Override
public void removeUpdate(DocumentEvent e)
{
documentChanged();
}
/**
* The border gap is used in calculating the left and right insets of the
* border. Default value is 5.
*
* @param borderGap the gap in pixels
*/
public void setBorderGap(int borderGap)
{
this.borderGap = borderGap;
Border inner = new EmptyBorder(0, borderGap, 0, borderGap);
setBorder( new CompoundBorder(OUTER, inner) );
lastDigits = 0;
setPreferredWidth();
}
/**
* The Color used to render the current line digits. Default is Coolor.RED.
*
* @param currentLineForeground the Color used to render the current line
*/
public void setCurrentLineForeground(Color currentLineForeground)
{
this.currentLineForeground = currentLineForeground;
}
/**
* Specify the horizontal alignment of the digits within the component.
* Common values would be:
* <ul>
* <li>TextLineNumber.LEFT
* <li>TextLineNumber.CENTER
* <li>TextLineNumber.RIGHT (default)
* </ul>
* @param currentLineForeground the Color used to render the current line
*/
public void setDigitAlignment(float digitAlignment)
{
this.digitAlignment =
digitAlignment > 1.0f ? 1.0f : digitAlignment < 0.0f ? -1.0f : digitAlignment;
}
/**
* Specify the mimimum number of digits used to calculate the preferred
* width of the component. Default is 3.
*
* @param minimumDisplayDigits the number digits used in the preferred
* width calculation
*/
public void setMinimumDisplayDigits(int minimumDisplayDigits)
{
this.minimumDisplayDigits = minimumDisplayDigits;
setPreferredWidth();
}
/**
* Calculate the width needed to display the maximum line number
*/
private void setPreferredWidth()
{
Element root = component.getDocument().getDefaultRootElement();
int lines = root.getElementCount();
int digits = Math.max(String.valueOf(lines).length(), minimumDisplayDigits);
// Update sizes when number of digits in the line number changes
if (lastDigits != digits)
{
lastDigits = digits;
FontMetrics fontMetrics = getFontMetrics( getFont() );
int width = fontMetrics.charWidth( '0' ) * digits;
Insets insets = getInsets();
int preferredWidth = insets.left + insets.right + width;
Dimension d = getPreferredSize();
d.setSize(preferredWidth, HEIGHT);
setPreferredSize( d );
setSize( d );
}
}
/**
* Set the update font property. Indicates whether this Font should be
* updated automatically when the Font of the related text component
* is changed.
*
* @param updateFont when true update the Font and repaint the line
* numbers, otherwise just repaint the line numbers.
*/
public void setUpdateFont(boolean updateFont)
{
this.updateFont = updateFont;
}
}

View File

@ -0,0 +1,77 @@
package tpsa;
import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JMenuItem;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.UndoManager;
/**
* Klasa implementująca UnDo
*
* @author Duga Eye
*
*/
public class UndoAction extends AbstractAction {
/**
*
*/
private static final long serialVersionUID = 5058740336403951058L;
private RedoAction redoAction;
private JMenuItem undoButton;
private UndoManager undoManager;
/**
* @param manager
* @param button
* @param action
*/
public UndoAction(UndoManager manager, JMenuItem button, RedoAction action) {
super("Undo");
BrainFuckIDE.lg.info("Creating instance of class");
setEnabled(false);
undoManager = manager;
undoButton = button;
redoAction = action;
}
@Override
public void actionPerformed(ActionEvent e) {
try {
// System.err.println("YEP");
undoManager.undo();
// System.err.println("WORKS");
} catch (CannotUndoException excp) {
BrainFuckIDE.lg.severe(BrainFuckIDE.ErrorsFormatter(excp));
}
update();
redoAction.update();
}
/**
* @param action
*/
public void setRedoAction(RedoAction action) {
redoAction = action;
}
protected void update() {
BrainFuckIDE.lg.fine("Updating undo/redo state");
if (undoManager.canUndo()) {
setEnabled(true);
undoButton.setEnabled(true);
putValue(Action.NAME, undoManager.getUndoPresentationName());
} else {
setEnabled(false);
undoButton.setEnabled(false);
putValue(Action.NAME, "Undo");
}
}
}

View File

@ -0,0 +1,34 @@
package tpsa.dialogs;
import java.awt.Dimension;
import javax.swing.JDialog;
import javax.swing.JFrame;
/**
* O programie dialog
*
* @author Duga Eye
*
*/
public class AboutProgramDialog extends JDialog {
/**
*
*/
private static final long serialVersionUID = -1176985069310756058L;
public AboutProgramDialog(JFrame frame)
{
super(frame, "About program", ModalityType.APPLICATION_MODAL);
setPreferredSize(new Dimension(500, 400));
pack();
}
public boolean showDialog() {
setVisible(true);
return true;
}
}

View File

@ -0,0 +1,190 @@
package tpsa.dialogs;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import javax.swing.AbstractAction;
import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRootPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextField;
import javax.swing.JTextPane;
import javax.swing.KeyStroke;
import javax.swing.WindowConstants;
import javax.swing.text.BadLocationException;
import tpsa.BFTab;
import tpsa.BrainFuckIDE;
import tpsa.MainFrame;
/**
* Dialog wyszukiwania wyrażenia
*
* @author Duga Eye
*
*/
public class FindDialog extends JDialog {
/**
*
*/
private static final long serialVersionUID = -486529548871883227L;
private JTextField match;
private boolean result;
/**
* @param frame
*/
public FindDialog(final MainFrame frame) {
super(frame);
setTitle("Find string");
setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE);
setModalityType(ModalityType.APPLICATION_MODAL);
setPreferredSize(new Dimension(250, 100));
Box b = Box.createVerticalBox();
JRootPane pane = getRootPane();
KeyStroke escapeStroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
KeyStroke enterStroke = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0);
pane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(escapeStroke,
"escape");
pane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(enterStroke,
"EnterFind");
pane.getActionMap().put("escape", new AbstractAction() {
/**
*
*/
private static final long serialVersionUID = -375905298222684550L;
@Override
public void actionPerformed(ActionEvent e) {
result = false;
setVisible(false);
}
});
pane.getActionMap().put("EnterFind", new AbstractAction() {
/**
*
*/
private static final long serialVersionUID = -8373549218006477790L;
@Override
public void actionPerformed(ActionEvent e) {
try {
BrainFuckIDE.lg.fine("Searching...");
JTabbedPane tabs = frame.getTabs();
BFTab tab = (BFTab) tabs.getSelectedComponent();
if (tab == null)
return;
String s = tab.getArea().getText();
String search = getMatch();
int oldPos = tab.getArea().getCaretPosition();
int pos = -1;
pos = s.indexOf(search, oldPos + 1);
if (pos < 0) {
BrainFuckIDE.lg.info("Rewinding to the beginning");
pos = s.indexOf(search);
}
BrainFuckIDE.lg.fine("Searching for: " + search);
BrainFuckIDE.lg.finest("found: " + pos);
if (pos >= 0) {
Rectangle rect = tab.getArea().modelToView(pos);
BrainFuckIDE.lg.finest("rect: " + rect.toString());
JTextPane area = tab.getArea();
area.scrollRectToVisible(rect);
area.setCaretPosition(pos);
} else
JOptionPane.showMessageDialog(frame,
"Not found from cursor phrase: " + search);
} catch (BadLocationException excp) {
BrainFuckIDE.lg.warning(BrainFuckIDE.ErrorsFormatter(excp));
}
}
});
Box b1 = Box.createHorizontalBox();
b1.add(new JLabel("Find: "));
match = new JTextField(10);
b1.add(match);
JPanel p2 = new JPanel();
p2.setLayout(new FlowLayout(FlowLayout.CENTER));
JButton find = new JButton("Find");
// JButton findNext = new JButton("Find next");
p2.add(find);
// p2.add(findNext);
b.add(b1);
Component gl = Box.createHorizontalGlue();
gl.setPreferredSize(new Dimension(10, 10));
b.add(gl);
b.add(p2);
find.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
result = true;
setVisible(false);
}
});
// add(find);
// add(findNext);
Component glue = Box.createVerticalGlue();
glue.setPreferredSize(new Dimension(Integer.MAX_VALUE,
Integer.MAX_VALUE));
b.add(glue);
add(b);
pack();
}
/**
* Wyrażenie do wyszukania
*
* @return Wyrażenie, które użytkownik chce wyszukać
*/
public String getMatch() {
return new String(match.getText());
}
/**
* Pokaż dialog
*
* @return Potwierdzenie/odrzucenie zmian
*/
public boolean showDialog() {
result = false;
setVisible(true);
return result;
}
}

View File

@ -0,0 +1,33 @@
package tpsa.dialogs;
import java.awt.Dimension;
import javax.swing.JDialog;
import javax.swing.JFrame;
/**
* Dialog pomocy
*
* @author Duga Eye
*
*/
public class HelpDialog extends JDialog{
/**
*
*/
private static final long serialVersionUID = 5854396908923903514L;
public HelpDialog(JFrame frame) {
super(frame, "Help", ModalityType.APPLICATION_MODAL);
setPreferredSize(new Dimension(500, 400));
pack();
}
public boolean showDialog() {
setVisible(true);
return true;
}
}

View File

@ -0,0 +1,147 @@
package tpsa.dialogs;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.Box;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRootPane;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import tpsa.BrainFuckIDE;
import tpsa.MainFrame;
/**
* Dialog ustalania pamięci VM BrainFucka
*
* @author Duga Eye
*
*/
public class MemorySizeDialog extends JDialog {
/**
*
*/
private static final long serialVersionUID = -6179258766072502357L;
private Action okAction;
private Action cancelAction;
private JTextField sizeEdit;
private boolean result = false;
public MemorySizeDialog(final MainFrame frame) {
super(frame);
setDefaultCloseOperation(JDialog.HIDE_ON_CLOSE);
setModalityType(ModalityType.APPLICATION_MODAL);
setPreferredSize(new Dimension(200, 150));
Box b = Box.createVerticalBox();
JPanel p1 = new JPanel(new FlowLayout(FlowLayout.LEFT));
p1.add(new JLabel("Podaj rozmiar VM Brainfucka: "));
sizeEdit = new JTextField(8);
p1.add(sizeEdit);
JButton ok = new JButton("OK");
JButton cancel = new JButton("Cancel");
JPanel p2 = new JPanel(new FlowLayout(FlowLayout.LEFT));
p2.add(ok);
p2.add(cancel);
b.add(p1);
b.add(p2);
okAction = new AbstractAction("OK") {
/**
*
*/
private static final long serialVersionUID = -5794707237614988673L;
@Override
public void actionPerformed(ActionEvent e) {
BrainFuckIDE.lg.info("ok action performed");
if (getSizeMemory() <= 0) {
JOptionPane
.showMessageDialog(frame,
"Wartość rozmiaru pamieci musi być typu Integer i >= 0");
return;
}
result = true;
setVisible(false);
}
};
cancelAction = new AbstractAction("Cancel") {
/**
*
*/
private static final long serialVersionUID = 8441237664658329642L;
@Override
public void actionPerformed(ActionEvent e) {
setVisible(false);
}
};
ok.setAction(okAction);
cancel.setAction(cancelAction);
JRootPane pane = getRootPane();
KeyStroke acceptStroke = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0);
KeyStroke cancelStroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
InputMap iMap = pane
.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
ActionMap aMap = pane.getActionMap();
iMap.put(acceptStroke, "accept");
aMap.put("accept", okAction);
iMap.put(cancelStroke, "cancel");
aMap.put("cancel", cancelAction);
add(b);
pack();
}
public int getSizeMemory() {
return Integer.parseInt(sizeEdit.getText());
}
public void setSizeMemory(int s) {
sizeEdit.setText("" + s);
}
/**
* Pokazuje ten wspaniały dialog
*
* @return w zasadzie nic, czy użytkownik zaakceptował wprowadzone dane
*/
public boolean showDialog() {
result = false;
setVisible(true);
return result;
}
}

View File

@ -0,0 +1,197 @@
package tpsa.dialogs;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JRootPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextField;
import javax.swing.JTextPane;
import javax.swing.KeyStroke;
import javax.swing.text.BadLocationException;
import tpsa.BFTab;
import tpsa.BrainFuckIDE;
import tpsa.MainFrame;
/**
* Dialog do zastępowania treści jednej drugą
*
* @author Duga Eye
*
*/
public class ReplaceDialog extends JDialog {
/**
*
*/
private static final long serialVersionUID = 103076309978197148L;
/**
* Wyrażenie, które ma znaleść
*/
private JTextField match;
/**
* Wyrażenie, którym ma zastąpić match
*/
private JTextField replace;
/**
* Czy użytkownik, zaakceptował dane
*/
private boolean success;
public ReplaceDialog(final MainFrame frame) {
super(frame);
setTitle("Replace");
setPreferredSize(new Dimension(250, 100));
setModalityType(ModalityType.APPLICATION_MODAL);
setTitle("Find");
setLayout(new GridLayout(3, 2));
JRootPane pane = getRootPane();
KeyStroke escapeStroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
pane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(escapeStroke,
"escape");
pane.getActionMap().put("escape", new AbstractAction() {
/**
*
*/
private static final long serialVersionUID = -3277168784395957597L;
@Override
public void actionPerformed(ActionEvent e) {
success = false;
setVisible(false);
}
});
add(new JLabel("Find: "));
match = new JTextField(10);
add(match);
replace = new JTextField(10);
add(new JLabel("Replace: "));
add(replace);
JButton replace = new JButton("Replace");
Action replaceAct = new AbstractAction("Replace one") {
/**
*
*/
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
try {
JTabbedPane tabs = frame.getTabs();
BFTab tab = (BFTab) tabs.getSelectedComponent();
JTextPane area = tab.getArea();
int cursorPos = area.getCaretPosition();
StringBuilder s = new StringBuilder(area.getText());
int posToChange = s.indexOf(getMatch(), cursorPos);
if (posToChange < 0) {
JOptionPane.showMessageDialog(frame,
"Nope, nie znalazłem ciągu do zastąpienia");
return;
// ретурн;
}
s.replace(posToChange, posToChange + getMatch().length(),
getReplace());
tab.getArea().setText(s.toString());
Rectangle rect = tab.getArea().modelToView(posToChange);
BrainFuckIDE.lg.finest("rect: " + rect.toString());
area.scrollRectToVisible(rect);
area.setCaretPosition(posToChange);
} catch (BadLocationException excp) {
BrainFuckIDE.lg.warning(BrainFuckIDE.ErrorsFormatter(excp));
JOptionPane.showMessageDialog(frame,
"Coś się stało, i właściwie nie chcę wiedzieć, co",
"Powitanie", JOptionPane.INFORMATION_MESSAGE);
}
}
};
replace.setAction(replaceAct);
KeyStroke replaceStroke = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0);
getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
replaceStroke, "replace");
getRootPane().getActionMap().put("replace", replaceAct);
JButton replaceAll = new JButton("Replace all");
replaceAll.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
JTabbedPane tabs = frame.getTabs();
BFTab tab = (BFTab) tabs.getSelectedComponent();
String s = tab.getArea().getText();
int count = ReplaceDialog.countOccurences(s, getMatch());
s = s.replaceAll(getMatch(), getReplace());
tab.getArea().setText(s);
JOptionPane.showMessageDialog(frame, "Sucess. Changed all "
+ count + " occurences of phrase: " + getMatch(),
"Abnormal error", JOptionPane.INFORMATION_MESSAGE);
}
});
add(replace);
add(replaceAll);
pack();
}
/**
* @return Wyrażenie, które użytkownik chce zastąpić
*/
public String getMatch() {
return new String(match.getText());
}
/**
* @return Wyrażenie, którym użytkownik chce zastąpić match
*/
public String getReplace() {
return new String(replace.getText());
}
/**
* @return Czy potwierdzono zmiany
*/
public boolean getSuccess() {
return success;
}
@Override
public void setVisible(boolean b) {
if (b == true)
success = false;
super.setVisible(b);
}
static int countOccurences(String s, String match) {
int count = 0;
int pos = -1;
while ((pos = s.indexOf(match, pos + 1)) >= 0)
++count;
return count;
}
}

View File

@ -0,0 +1,29 @@
package tpsa.dialogs.filters;
import java.io.File;
import javax.swing.filechooser.FileFilter;
/**
* Filtr rozszerzeń kodów źródłowych BrainFucka
*
* @author Duga Eye
*
*/
public class BFFileFilter extends FileFilter {
@Override
public boolean accept(File f) {
if (f.isDirectory())
return true;
if (f.getName().endsWith(".bf"))
return true;
return false;
}
@Override
public String getDescription() {
return "BrainFuck source code";
}
}

View File

@ -0,0 +1,62 @@
package tpsa.highlighters;
import javax.swing.text.Style;
import javax.swing.text.StyledDocument;
/**
* Reprezentuje klasę coś ala TODO dla swing utilities co ma odmalować
* odpowiednim kolorem
*
* @author Duga Eye
*/
public class StyleHighlight {
private StyledDocument doc;
private int length;
private int offset;
private Style style;
public StyleHighlight() {
}
public StyleHighlight(StyledDocument doc, int offset, int length,
Style style) {
this.doc = doc;
this.offset = offset;
this.length = length;
this.style = style;
}
public StyledDocument getDoc() {
return doc;
}
public int getLength() {
return length;
}
public int getOffset() {
return offset;
}
public Style getStyle() {
return style;
}
public void setDoc(StyledDocument doc) {
this.doc = doc;
}
public void setLength(int length) {
this.length = length;
}
public void setOffset(int offset) {
this.offset = offset;
}
public void setStyle(Style style) {
this.style = style;
}
}

View File

@ -0,0 +1,192 @@
package tpsa.highlighters;
import java.util.ArrayList;
import java.util.Arrays;
import javax.swing.SwingUtilities;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Style;
import javax.swing.text.StyledDocument;
import tpsa.BrainFuckIDE;
/**
* Wyróżnia kod od komentarzy
*
* @author Duga Eye
*
*/
public class SyntaxHighlighter implements DocumentListener {
public static ArrayList<Character> BFcharacterList = new ArrayList<Character>(
Arrays.asList(new Character[] { '>', '<', '+', '-', '.', ',', '[',
']', 'b' }));
final ArrayList<StyleHighlight> list = new ArrayList<StyleHighlight>();
public SyntaxHighlighter() {
}
private void addToTheList(StyledDocument doc, final int offset,
Style comment, int from, final ArrayList<StyleHighlight> list,
int position) {
int length;
length = position - from;
if (length <= 0)
return;
synchronized (list) {
list.add(new StyleHighlight(doc, offset + from, length, comment));
}
}
@Override
public void changedUpdate(DocumentEvent e) {
// update(e);
}
private void checkForBugs(StyledDocument doc) {
try {
String s = doc.getText(0, doc.getLength());
Style errorStyle = doc.getStyle("ERROR");
int indentation = 0;
int idx = 0;
while (idx < s.length()) {
char c = 0;
do {
c = s.charAt(idx++);
} while (c != '[' && c != ']' && idx < s.length());
switch (c) {
case '[':
indentation++;
break;
case ']':
if (--indentation < 0) {
indentation = 0;
list.add(new StyleHighlight(doc, idx - 1, 1, errorStyle));
}
}
}
updateHighlights();
} catch (BadLocationException excp) {
BrainFuckIDE.lg.severe(BrainFuckIDE.ErrorsFormatter(excp));
}
}
@Override
public void insertUpdate(DocumentEvent e) {
update(e);
}
@Override
public void removeUpdate(DocumentEvent e) {
update(e);
}
private void setStyleComment(StyledDocument doc, final int offset,
String s, Style comment) {
BrainFuckIDE.lg.finest("Text: '" + s + "'");
Style sCode = doc.getStyle("CODE");
int state = 0;
int from = 0;
synchronized (sCode) {
list.clear();
}
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
switch (state) {
case 0: // seria kodu
if (!BFcharacterList.contains(c)) {
addToTheList(doc, offset, sCode, from, list, i);
from = i;
state = 1;
}
break;
case 1: // seria komentarzy
if (BFcharacterList.contains(c)) {
addToTheList(doc, offset, comment, from, list, i);
from = i;
state = 0;
}
break;
}
}
if (state == 1) {
addToTheList(doc, offset, comment, from, list, s.length());
} else {
addToTheList(doc, offset, sCode, from, list, s.length());
}
}
public void update(StyledDocument doc, int offset, int length,
boolean machine) {
Style comment = doc.getStyle("COMMENT");
String s;
try {
BrainFuckIDE.lg.finer("Change to analyse: " + offset + ", "
+ length + ", length: " + doc.getLength());
s = doc.getText(offset, Math.min(length, doc.getLength() - offset));
setStyleComment(doc, offset, s, comment);
checkForBugs(doc);
checkBreakpoints(doc);
updateHighlights();
} catch (BadLocationException excp) {
BrainFuckIDE.lg.severe(BrainFuckIDE.ErrorsFormatter(excp));
}
}
private void checkBreakpoints(StyledDocument doc) {
/*
* try { BrainFuckIDE.lg.finer("Check breapoints"); String s =
* doc.getText(0, doc.getLength()); int pos = -1; Style sBreakpoint =
* doc.getStyle("BREAKPOINT"); while ((pos = s.indexOf('b', pos + 1)) >=
* 0) { BrainFuckIDE.lg.finer("Breakpoint pos: " + pos); int i; for (i =
* 0; i < s.length() - pos && s.charAt(i + pos) != 'b'; i++) ;
*
* synchronized (list) { list.add(new StyleHighlight(doc, pos, i - 1,
* sBreakpoint)); } pos += i; }
*
* } catch (BadLocationException excp) { }
*/
}
private void update(DocumentEvent e) {
update((StyledDocument) e.getDocument(), e.getOffset(), e.getLength(),
false);
}
private void updateHighlights() {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
synchronized (list) {
for (StyleHighlight h : list)
h.getDoc().setCharacterAttributes(h.getOffset(),
h.getLength(), h.getStyle(), true);
list.clear();
}
}
});
}
}

View File

@ -0,0 +1,134 @@
package tpsa.highlighters;
import java.awt.Color;
import java.util.Locale;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultHighlighter;
import javax.swing.text.Document;
import tpsa.BrainFuckIDE;
public class WordsHighlighter extends DefaultHighlighter implements
DocumentListener {
/**
*
*/
private Long last = 0L;
/**
* Words to highlight
* Words to highlight TODO not implemented
*/
private String[] words;
/**
* @param word
*/
public WordsHighlighter(String[] words) {
// FIXME shallow copy
this.words = words.clone();
}
@Override
public void changedUpdate(DocumentEvent e) {
updateFrequent(e);
updateNotToFrequent(e);
}
@Override
public void insertUpdate(DocumentEvent e) {
updateFrequent(e);
updateNotToFrequent(e);
}
@Override
public void removeUpdate(DocumentEvent e) {
updateFrequent(e);
updateNotToFrequent(e);
}
/**
* @param s
* @param start
* @param length
* TODO
* @throws BadLocationException
*/
private void searchForOccurance(String s, int start, int length)
throws BadLocationException {
DefaultHighlightPainter todoHighLighter = new DefaultHighlightPainter(
Color.LIGHT_GRAY);
int pos = -1;
String copy = new String(s).toUpperCase(Locale.forLanguageTag("pl_PL"));
for (int i = 0; i < words.length; i++) {
pos = -1;
while ((pos = copy.indexOf(words[i], pos + 1)) >= 0) {
int endOfHighlighting = Math.min(pos + words[i].length()
+ start, length - 1);
addHighlight(pos + start, endOfHighlighting,
todoHighLighter);
}
}
}
/**
* @param e
*/
private void update(DocumentEvent e) {
try {
Document doc = e.getDocument();
String s = doc.getText(0, doc.getLength());
removeAllHighlights();
searchForOccurance(s, 0, doc.getLength());
} catch (BadLocationException excp) {
BrainFuckIDE.lg.severe(BrainFuckIDE.ErrorsFormatter(excp));
}
}
/**
* @param e
*/
private void updateFrequent(DocumentEvent e) {
int offset = e.getOffset();
int length = e.getLength();
Document doc = e.getDocument();
try {
int start = Math.max(0, offset - 10);
int end = Math.min(doc.getLength() - start, length + 20);
String s = doc.getText(start, end);
Highlight[] highlights = getHighlights();
for (int i = 0; i < highlights.length; i++)
if (highlights[i].getStartOffset() >= offset
&& highlights[i].getEndOffset() <= offset + length)
removeHighlight(highlights[i]);
searchForOccurance(s, start, doc.getLength());
} catch (BadLocationException excp) {
BrainFuckIDE.lg.severe(BrainFuckIDE.ErrorsFormatter(excp));
}
}
/**
* @param e
*/
private void updateNotToFrequent(DocumentEvent e) {
long now = System.nanoTime();
long diff = now - last;
if (diff > 2e9) {
last = now;
update(e);
}
}
}

View File

@ -0,0 +1,48 @@
package tv.porst.jhexview;
import java.awt.Color;
public class ColoredRange implements Comparable<ColoredRange> {
private final Color fcolor;
private final long start;
private final int size;
private final Color bgcolor;
public ColoredRange(final long start, final int size, final Color fcolor,
final Color bgcolor) {
this.start = start;
this.size = size;
this.fcolor = fcolor;
this.bgcolor = bgcolor;
}
@Override
public int compareTo(final ColoredRange arg0) {
return (int) (start - arg0.start);
}
public boolean containsOffset(final long offset) {
return offset >= start && offset < start + size;
}
public Color getBackgroundColor() {
return bgcolor;
}
public Color getColor() {
return fcolor;
}
public int getSize() {
return size;
}
public long getStart() {
return start;
}
}

View File

@ -0,0 +1,137 @@
package tv.porst.jhexview;
import java.util.ArrayList;
import java.util.Collections;
public final class ColoredRangeManager {
private final ArrayList<ColoredRange> ranges = new ArrayList<ColoredRange>();
public void addRange(final ColoredRange range) {
ranges.add(range);
Collections.sort(ranges);
}
public void clear() {
ranges.clear();
}
public ColoredRange findRange(final long offset) {
for (final ColoredRange range : ranges) {
if (range.getStart() >= offset) {
return range;
}
}
return null;
}
public ColoredRange findRangeWith(final long offset) {
for (final ColoredRange range : ranges) {
if (range.containsOffset(offset)) {
return range;
}
}
return null;
}
public void removeRange(final long offset, final int size) {
// Try to find the range that contains the offset
ColoredRange range = findRangeWith(offset);
// If there is no such range, at least find the range right
// before the offset.
if (range == null) {
range = findRange(offset);
}
// If there is no such range then there is nothing to remove.
if (range == null) {
return;
}
// TODO: The <= is a hack; used to be just ==; Check this again
if (offset <= range.getStart()) {
// The offset starts exactly at the range. That means
// we start decolorizing at the beginning of the range.
if (range.getSize() == size) {
// If the sizes are equal the entire range is decolorized and
// we're done.
ranges.remove(range);
} else if (range.getSize() < size) {
// If the range is smaller the entire range is decolorized and
// the
// remaining bytes are decolorized in the next step.
ranges.remove(range);
removeRange(range.getStart() + range.getSize(),
size - range.getSize());
} else
// if (range.getSize() > size)
{
// If the range is larger than the decolorizing area, ditch the
// range and add a new range that contains the area that stays
// colorized.
ranges.remove(range);
addRange(new ColoredRange(offset + size,
range.getSize() - size, range.getColor(),
range.getBackgroundColor()));
}
} else
// if (offset > range.getStart())
{
// We start to decolorize somewhere in the middle of the range.
if (offset + size == range.getStart() + range.getSize()) {
// If the last offset of the range and the decolorizing area is equal,
// then the latter part of the range is ditched. Get rid of the range
// and create a new range that represents the first part of the area.
ranges.remove(range);
final long newStart = range.getStart();
final int newSize = range.getSize() - size;
// int newSize = (int)(offset - range.getStart());
addRange(new ColoredRange(newStart, newSize, range.getColor(), range.getBackgroundColor()));
} else if (offset + size < range.getStart() + range.getSize()) {
// Some part is cut out of the middle of the range. Get rid of the old range
// and create two new ranges for the first part and last part of the old range.
ranges.remove(range);
final long newStartFirst = range.getStart();
final int newSizeFirst = (int) (offset - range.getStart());
addRange(new ColoredRange(newStartFirst, newSizeFirst,
range.getColor(), range.getBackgroundColor()));
final long newStartLast = offset + size;
final int newSizeLast = (int) (range.getStart()
+ range.getSize() - offset - size);
addRange(new ColoredRange(newStartLast, newSizeLast,
range.getColor(), range.getBackgroundColor()));
} else
// if (offset + size > range.getStart() + range.getSize())
{
// More than the current range must be decolorized. That means
// the entire range
// except for the start is ditched.
ranges.remove(range);
final long newStart = range.getStart();
final int newSize = (int) (offset - range.getStart());
addRange(new ColoredRange(newStart, newSize, range.getColor(), range.getBackgroundColor()));
removeRange(range.getStart() + range.getSize(), size - (int) (range.getStart() + range.getSize() - offset));
}
}
}
}

View File

@ -0,0 +1,41 @@
package tv.porst.jhexview;
import java.awt.Color;
public interface IColormap {
/**
* Determines whether the byte at the given offset should be
* colored or not.
*
* @param data The data array that can be used to determine the return value.
* @param currentOffset The offset of the byte in question.
*
* @return True if the byte should be colored. False, otherwise.
*/
boolean colorize(final byte[] data, final long currentOffset);
/**
* Returns the background color that should be used to color
* the byte at the given offset.
*
* @param data The data array that can be used to determine the return value.
* @param currentOffset The offset of the byte in question.
*
* @return The background color to be used by that byte. Null, if
* the default background color should be used,
*/
Color getBackgroundColor(final byte[] data, final long currentOffset);
/**
* Returns the foreground color that should be used to color
* the byte at the given offset.
*
* @param data The data array that can be used to determine the return value.
* @param currentOffset The offset of the byte in question.
*
* @return The foreground color to be used by that byte. Null, if
* the default foreground color should be used,
*/
Color getForegroundColor(final byte[] data, final long currentOffset);
}

View File

@ -0,0 +1,7 @@
package tv.porst.jhexview;
public interface IDataChangedListener {
void dataChanged();
}

View File

@ -0,0 +1,22 @@
package tv.porst.jhexview;
public interface IDataProvider {
void addListener(final IDataChangedListener hexView);
byte[] getData();
byte[] getData(long offset, int length);
int getDataLength();
boolean hasData(long start, int length);
boolean isEditable();
boolean keepTrying();
void removeListener(IDataChangedListener listener);
void setData(long offset, byte[] data);
}

View File

@ -0,0 +1,7 @@
package tv.porst.jhexview;
public interface IHexViewListener {
void selectionChanged(long start, long length);
}

View File

@ -0,0 +1,22 @@
package tv.porst.jhexview;
import javax.swing.JPopupMenu;
/**
* This interface must be implemented by all classes that want to provide
* context menus for the JHexView control.
*
*/
public interface IMenuCreator {
/**
* This function is called to generate a popup menu after the user
* right-clicked somewhere in the hex control.
*
* @param offset The offset of the right-click.
*
* @return The popup menu suitable for that offset or null if no popup menu
* should be shown.
*/
JPopupMenu createMenu(long offset);
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,64 @@
package tv.porst.jhexview;
import java.util.Arrays;
/**
* Data provider that provides data to the hex view component from a
* static array. Use this data provider if you already have all the data
* in memory and do not have to reload memory from an external source.
*/
public final class SimpleDataProvider implements IDataProvider {
private final byte[] m_data;
public SimpleDataProvider(final byte[] data) {
this.m_data = data;
}
@Override
public void addListener(final IDataChangedListener listener) {
}
@Override
public byte[] getData() {
return getData(0L, getDataLength());
}
@Override
public byte[] getData(final long offset, final int length) {
return Arrays.copyOfRange(this.m_data, (int) offset, (int) (offset + length));
}
@Override
public int getDataLength() {
return this.m_data.length;
}
public long getOffset() {
return 0L;
}
@Override
public boolean hasData(final long offset, final int length) {
return true;
}
@Override
public boolean isEditable() {
return true;
}
@Override
public boolean keepTrying() {
return false;
}
@Override
public void removeListener(final IDataChangedListener listener) {
}
@Override
public void setData(final long offset, final byte[] data) {
System.arraycopy(data, 0, this.m_data, (int) offset, data.length);
}
}

View File

@ -0,0 +1,83 @@
package tv.porst.splib.arrays;
/**
* Contains helper functions for working with arrays.
*/
public final class ArrayHelpers {
/**
* Returns a sub array of a given array.
*
* @param source The source array from which the sub array is extracted.
* @param offset The start offset of the sub array.
* @param length The length of the sub array in bytes.
*
* @return The sub array.
*/
public static byte[] getSubArray(final byte[] source, final int offset, final int length) {
if (source == null) {
throw new IllegalArgumentException("Source argument must not be null.");
}
if (offset < 0) {
throw new IllegalArgumentException("Start offset must not be negative.");
}
if (offset >= source.length) {
throw new IllegalArgumentException("Start offset is too big.");
}
if (length < 0) {
throw new IllegalArgumentException("Length must not be negative.");
}
if (offset + length > source.length) {
throw new IllegalArgumentException("Range is too big.");
}
final byte[] subArray = new byte[length];
System.arraycopy(source, offset, subArray, 0, length);
return subArray;
}
/**
* Removes data from a byte array.
*
* @param data The input byte array.
* @param startOffset The start offset of the data to remove.
* @param length Length in bytes of the data to remove.
*
* @return The new array without the removed data.
*/
public static byte[] removeData(final byte[] data, final int startOffset, final int length) {
final byte[] newData = new byte[data.length - length];
System.arraycopy(data, 0, newData, 0, startOffset);
System.arraycopy(data, startOffset + length, newData, startOffset, data.length - startOffset - length);
return newData;
}
/**
* Replaces data in a byte array.
*
* @param data The input byte array.
* @param startOffset The start offset of the data to change.
* @param length The length in bytes of the data to change.
* @param replacementValue The new value for each byte in the replacement range.
*
* @return The new array with the replaced data.
*/
public static byte[] replaceData(final byte[] data, final int startOffset, final int length, final byte replacementValue) {
final byte[] newData = data.clone();
for (int i=startOffset;i<startOffset+length;i++) {
newData[i] = replacementValue;
}
return newData;
}
}

View File

@ -0,0 +1,5 @@
package tv.porst.splib.arrays;
/**
* This package contains classes for working with arrays.
*/

View File

@ -0,0 +1,35 @@
package tv.porst.splib.binaryparser;
/**
* Represents a parsed ASCII string.
*/
public final class AsciiString extends ParsedType<String> implements IFileElement {
/**
* Flag that says whether the parsed string was zero-terminated or not.
*/
private final boolean zeroTerminated;
/**
* Creates a new AsciiString object.
*
* @param bytePosition Byte position of the string in the input stream.
* @param value String value.
* @param zeroTerminated Flag that says whether the parsed string was zero-terminated or not.
*/
public AsciiString(final int bytePosition, final String value, final boolean zeroTerminated) {
super(bytePosition, (value.length() + (zeroTerminated ? 1 : 0)) * 8, value);
this.zeroTerminated = zeroTerminated;
}
/**
* Returns the flag that says whether the parsed string was zero-terminated or not.
*
* @return The flag that says whether the parsed string was zero-terminated or not.
*/
public boolean isZeroTerminated() {
return zeroTerminated;
}
}

View File

@ -0,0 +1,531 @@
package tv.porst.splib.binaryparser;
/**
* Class that can be used to parse simple data structures from
* byte streams.
*/
public class BinaryParser {
/**
* The data to parse.
*/
private final byte[] data;
/**
* The current byte position.
*/
private int bytePosition = 0;
/**
* The current bit position.
*/
private int bitPosition = 0;
/**
* Creates a new binary parser object that parses data from
* the given byte array.
*
* @param data The source array.
*/
public BinaryParser(final byte[] data) {
if (data == null) {
throw new IllegalArgumentException("Data argument must not be null");
}
this.data = data.clone();
}
/**
* Aligns the parser to the next byte.
*/
public void align() {
if (bitPosition != 0) {
bytePosition++;
bitPosition = 0;
}
}
/**
* Returns the current bit position.
*
* @return The current bit position.
*/
public int getBitPosition() {
return bitPosition;
}
/**
* Returns the current byte position.
*
* @return The current byte position.
*/
public int getBytePosition() {
return bytePosition;
}
/**
* Returns the length of the byte stream.
*
* @return The length of the byte stream.
*/
public int getLength() {
return data.length;
}
/**
* Returns whether parsing the whole byte stream is complete.
*
* @return True, if parsing is complete. False, if there is data left to parse.
*/
public boolean isDone() {
return bytePosition == data.length;
}
/**
* Peeks at the next few bits without moving the current parsing
* position forward.
*
* @param numberOfBits The number of bits to peek at. This value must be between 0 and 32.
*
* @return The peeked bits.
*/
public UBits peekBits(final int numberOfBits) {
if (numberOfBits < 0 || numberOfBits > 32) {
throw new IllegalArgumentException("Number of bits argument must be between 0 and 32");
}
final int bytePosition = this.bytePosition;
final int bitPosition = this.bitPosition;
final UBits value = readBits(numberOfBits);
this.bitPosition = bitPosition;
this.bytePosition = bytePosition;
return value;
}
/**
* Peeks at the next UINT16 value without moving the current parsing
* position forward.
*
* @return The peeked UINT16 value.
*/
public UINT16 peekUInt16() {
final int bytePosition = this.bytePosition;
final int bitPosition = this.bitPosition;
final UINT16 value = readUInt16();
this.bitPosition = bitPosition;
this.bytePosition = bytePosition;
return value;
}
/**
* Peeks at the next UINT32 value without moving the current parsing
* position forward.
*
* @return The peeked UINT32 value.
*/
public UINT32 peekUInt32() {
final int bytePosition = this.bytePosition;
final int bitPosition = this.bitPosition;
final UINT32 value = readUInt32();
this.bitPosition = bitPosition;
this.bytePosition = bytePosition;
return value;
}
/**
* Peeks at the next UINT8 value without moving the current parsing
* position forward.
*
* @return The peeked UINT8 value.
*/
public UINT8 peekUInt8() {
final int bytePosition = this.bytePosition;
final int bitPosition = this.bitPosition;
final UINT8 value = readUInt8();
this.bitPosition = bitPosition;
this.bytePosition = bytePosition;
return value;
}
/**
* Reads the next few bits from the byte stream.
*
* @param numberOfBits The number of bits to read. This value must be between 0 and 32.
*
* @return The read bits.
*/
public UBits readBits(final int numberOfBits) {
if (bytePosition * 8 + bitPosition + numberOfBits > data.length * 8) {
throw new IllegalArgumentException("Not enough data left");
}
final int oldBytePosition = bytePosition;
final int oldBitPosition = bitPosition;
int value = 0;
for (int i=0;i<numberOfBits;i++) {
value = (value << 1) | ((data[bytePosition] >> (7 - bitPosition)) & 1);
bitPosition++;
if (bitPosition == 8) {
bitPosition = 0;
bytePosition++;
}
}
return new UBits(8 * oldBytePosition + oldBitPosition, numberOfBits, value);
}
/**
* Reads the next byte from the byte stream.
*
* @return The read byte.
*/
public byte readByte() {
if (bytePosition * 8 + bitPosition + 8 > data.length * 8) {
throw new IllegalArgumentException("Not enough data left");
}
if (bitPosition != 0) {
throw new IllegalStateException("Parser is not byte aligned");
}
return data[bytePosition++];
}
/**
* Reads the next bit from the byte stream.
*
* @return The read bit.
*/
public Flag readFlag() {
final int bytePosition = this.bytePosition;
final int bitPosition = this.bitPosition;
final UBits value = readBits(1);
return new Flag(8 * bytePosition + bitPosition, value.value() == 1);
}
/**
* Reads the next float from the byte stream.
*
* @return The next float.
*/
public Float32 readFloat() {
final float value = Float.intBitsToFloat(readInt32().value());
return new Float32(8 * bytePosition + bitPosition - 32, value);
}
/**
* Reads the next short float from the byte stream.
*
* @return The next short float.
*/
public Float16 readFloat16() {
readInt16();
return new Float16(8 * bytePosition + bitPosition - 16, (float) 0.0);
}
/**
* Reads the next float from the byte stream.
*
* @return The next float.
*/
public Float64 readFloat64() {
final double value = Double.longBitsToDouble(readInt64().value());
return new Float64(8 * bytePosition + bitPosition - 64, value);
}
/**
* Reads the next short integer from the byte stream.
*
* @return The next short integer.
*/
public INT16 readInt16() {
if (bytePosition * 8 + bitPosition + 16 > data.length * 8) {
throw new IllegalArgumentException("Not enough data left");
}
if (bitPosition != 0) {
throw new IllegalStateException("Parser is not byte aligned");
}
final int firstByte = readByte() & 0xFF;
final int secondByte = readByte() & 0xFF;
return new INT16(8 * bytePosition + bitPosition - 16, secondByte << 8 | firstByte);
}
/**
* Reads the next three-byte integer from the byte stream.
*
* @return The next three-byte integer.
*/
public INT24 readInt24() {
if (bytePosition * 8 + bitPosition + 24 > data.length * 8) {
throw new IllegalArgumentException("Not enough data left");
}
if (bitPosition != 0) {
throw new IllegalStateException("Parser is not byte aligned");
}
final int firstByte = readByte() & 0xFF;
final int secondByte = readByte() & 0xFF;
final int thirdByte = readByte() & 0xFF;
int value = thirdByte << 16 | secondByte << 8 | firstByte;
if (value > 0x7FFFFF) {
value = value - 0x1000000;
}
return new INT24(8 * bytePosition + bitPosition - 24, value);
}
/**
* Reads the next integer from the byte stream.
*
* @return The next integer.
*/
public INT32 readInt32() {
if (bytePosition * 8 + bitPosition + 32 > data.length * 8) {
throw new IllegalArgumentException("Not enough data left");
}
if (bitPosition != 0) {
throw new IllegalStateException("Parser is not byte aligned");
}
final int firstByte = readByte() & 0xFF;
final int secondByte = readByte() & 0xFF;
final int thirdByte = readByte() & 0xFF;
final int fourthByte = readByte() & 0xFF;
return new INT32(8 * bytePosition + bitPosition - 32, fourthByte << 24 | thirdByte << 16 | secondByte << 8 | firstByte);
}
/**
* Reads the next long from the byte stream.
*
* @return The next integer.
*/
public INT64 readInt64() {
if (bytePosition * 8 + bitPosition + 64 > data.length * 8) {
throw new IllegalArgumentException("Not enough data left");
}
if (bitPosition != 0) {
throw new IllegalStateException("Parser is not byte aligned");
}
final int firstByte = readByte() & 0xFF;
final int secondByte = readByte() & 0xFF;
final int thirdByte = readByte() & 0xFF;
final int fourthByte = readByte() & 0xFF;
final int fifthByte = readByte() & 0xFF;
final int sixthByte = readByte() & 0xFF;
final int seventhByte = readByte() & 0xFF;
final int eigthByte = readByte() & 0xFF;
return new INT64(8 * bytePosition + bitPosition - 64, eigthByte << 56 | seventhByte << 48 | sixthByte << 40 | fifthByte << 32 | fourthByte << 24 | thirdByte << 16 | secondByte << 8 | firstByte);
}
/**
* Reads the next few signed bits from the byte stream.
*
* @param numberOfBits The number of signed bits to read. This value must be between 0 and 32.
*
* @return The read signed bits.
*/
public Bits readSBits(final int numberOfBits) {
if (bytePosition * 8 + bitPosition + numberOfBits > data.length * 8) {
throw new IllegalArgumentException("Not enough data left");
}
final int oldBytePosition = bytePosition;
final int oldBitPosition = bitPosition;
int value = 0;
for (int i=0;i<numberOfBits;i++) {
value = (value << 1) | ((data[bytePosition] >> (7 - bitPosition)) & 1);
bitPosition++;
if (bitPosition == 8) {
bitPosition = 0;
bytePosition++;
}
}
return new Bits(8 * oldBytePosition + oldBitPosition, numberOfBits, value);
}
/**
* Reads the next 0-terminated ASCII string from the byte stream.
*
* @return The read string.
*/
public AsciiString readString() {
final int bytePosition = this.bytePosition;
final int bitPosition = this.bitPosition;
final StringBuffer value = new StringBuffer();
byte b;
while ((b = readByte()) != 0)
{
value.append((char) b);
}
return new AsciiString(8 * bytePosition + bitPosition, value.toString(), true);
}
/**
* Reads the next number of bytes from the byte stream into an ASCII string.
*
* @param numberOfBytes The number of bytes to add to the string.
*
* @return The read string.
*/
public AsciiString readString(final int numberOfBytes) {
if (numberOfBytes < 0) {
throw new IllegalArgumentException("The number of bytes in the string can not be negative");
}
final StringBuffer value = new StringBuffer();
for (int i=0;i<numberOfBytes;i++)
{
value.append((char) readByte());
}
return new AsciiString(8 * bytePosition - 8 * numberOfBytes, value.toString(), false);
}
/**
* Reads the next unsigned short integer from the byte stream.
*
* @return The next unsigned short integer.
*/
public UINT16 readUInt16() {
if (bytePosition * 8 + bitPosition + 16 > data.length * 8) {
throw new IllegalArgumentException("Not enough data left");
}
if (bitPosition != 0) {
throw new IllegalStateException("Parser is not byte aligned");
}
final int firstByte = readByte() & 0xFF;
final int secondByte = readByte() & 0xFF;
return new UINT16(8 * bytePosition + bitPosition - 16, secondByte << 8 | firstByte);
}
/**
* Reads the next unsigned integer from the byte stream.
*
* @return The next unsigned integer.
*/
public UINT32 readUInt32() {
if (bytePosition * 8 + bitPosition + 32 > data.length * 8) {
throw new IllegalArgumentException("Not enough data left");
}
if (bitPosition != 0) {
throw new IllegalStateException("Parser is not byte aligned");
}
final int firstByte = readByte() & 0xFF;
final int secondByte = readByte() & 0xFF;
final int thirdByte = readByte() & 0xFF;
final int fourthByte = readByte() & 0xFF;
return new UINT32(8 * bytePosition + bitPosition - 32, fourthByte << 24 | thirdByte << 16 | secondByte << 8 | firstByte);
}
/**
* Reads the next unsigned byte from the byte stream.
*
* @return The next unsigned byte.
*/
public UINT8 readUInt8() {
if (bytePosition * 8 + bitPosition + 32 > data.length * 8) {
throw new IllegalArgumentException("Not enough data left");
}
if (bitPosition != 0) {
throw new IllegalStateException("Parser is not byte aligned");
}
final int firstByte = readByte() & 0xFF;
return new UINT8(8 * bytePosition + bitPosition - 8, firstByte);
}
/**
* Sets the current read position in the byte stream.
*
* @param bytePosition The new byte position.
* @param bitPosition The new bit position.
*/
public void setPosition(final int bytePosition, final int bitPosition) {
if (bytePosition < 0) {
throw new IllegalArgumentException("Byte position argument must not be negative");
}
if (bitPosition < 0) {
throw new IllegalArgumentException("Bit position argument must not be negative");
}
if (bytePosition * 8 + bitPosition > data.length * 8) {
throw new IllegalArgumentException("Can not move read position beyond the end of the input buffer");
}
this.bytePosition = bytePosition;
this.bitPosition = bitPosition;
}
}

View File

@ -0,0 +1,16 @@
package tv.porst.splib.binaryparser;
/**
* Exception class used to signal exceptions during binary stream parsing.
*/
public final class BinaryParserException extends RuntimeException {
/**
* Creates a new exception object.
*
* @param message The exception message.
*/
public BinaryParserException(final String message) {
super(message);
}
}

View File

@ -0,0 +1,82 @@
package tv.porst.splib.binaryparser;
/**
* Contains helper functions for working with the binary parser.
*/
public final class BinaryParserHelpers {
/**
* Checks whether a certain number of bits can be read from the input stream before
* the end of the stream is reached.
*
* @param parser The parser whose stream is checked.
* @param numberOfBits The number of bits to check for.
*
* @return True, if there are the requested number of bits left in the input stream. False, otherwise.
*/
public static boolean hasBitsLeft(final BinaryParser parser, final long numberOfBits) {
if (parser == null) {
throw new IllegalArgumentException("Parser argument must not be null");
}
if (numberOfBits < 0) {
throw new IllegalArgumentException("Number of requested bits can not be negative");
}
return parser.getBytePosition() * 8 + parser.getBitPosition() + numberOfBits <= parser.getLength() * 8;
}
/**
* Checks whether a certain number of bytes can be read from the input stream before
* the end of the stream is reached.
*
* @param parser The parser whose stream is checked.
* @param numberOfBytes The number of bytes to check for.
*
* @return True, if there are the requested number of bytes left in the input stream. False, otherwise.
*/
public static boolean hasBytesLeft(final BinaryParser parser, final long numberOfBytes) {
if (parser == null) {
throw new IllegalArgumentException("Parser argument must not be null");
}
if (numberOfBytes < 0) {
throw new IllegalArgumentException("Number of requested bytes can not be negative");
}
return hasBitsLeft(parser, numberOfBytes * 8);
}
/**
* Reads a byte array from the input stream of a parser.
*
* @param parser The parser from which the byte array is read.
* @param length The length of the byte array to read.
*
* @return The byte array.
*/
public static byte[] readByteArray(final BinaryParser parser, final int length) {
if (parser == null) {
throw new IllegalArgumentException("Parser argument must not be null");
}
if (length < 0) {
throw new IllegalArgumentException("The length of the byte array must not be negative");
}
if (!hasBytesLeft(parser, length)) {
throw new IllegalArgumentException("Not enough bytes left in the input stream");
}
final byte[] array = new byte[length];
for (int i=0;i<array.length;i++) {
array[i] = parser.readByte();
}
return array;
}
}

View File

@ -0,0 +1,18 @@
package tv.porst.splib.binaryparser;
/**
* Represents a parsed bit field.
*/
public final class Bits extends ParsedType<Integer> {
/**
* Creates a new bit field object.
*
* @param bitPosition Bit position of the bit field in the input stream.
* @param bitLength Length of the field in bits.
* @param value Bit field value.
*/
public Bits(final int bitPosition, final int bitLength, final Integer value) {
super(bitPosition, bitLength, value);
}
}

View File

@ -0,0 +1,17 @@
package tv.porst.splib.binaryparser;
/**
* Represents a parsed flag.
*/
public final class Flag extends ParsedType<Boolean> {
/**
* Creates a new flag object.
*
* @param bitPosition Bit position of the flag in the input stream.
* @param value Floag value.
*/
public Flag(final int bitPosition, final boolean value) {
super(bitPosition, 1, value);
}
}

View File

@ -0,0 +1,17 @@
package tv.porst.splib.binaryparser;
/**
* Represents a parsed short float.
*/
public class Float16 extends ParsedType<Float> {
/**
* Creates a new short float object.
*
* @param bytePosition Byte position of the short float in the input stream.
* @param value Float value.
*/
public Float16(final int bytePosition, final Float value) {
super(bytePosition, 16, value);
}
}

View File

@ -0,0 +1,17 @@
package tv.porst.splib.binaryparser;
/**
* Represents a parsed float.
*/
public final class Float32 extends ParsedType<Float> {
/**
* Creates a new float object.
*
* @param bytePosition Byte position of the float in the input stream.
* @param value Float value.
*/
public Float32(final int bytePosition, final Float value) {
super(bytePosition, 32, value);
}
}

View File

@ -0,0 +1,17 @@
package tv.porst.splib.binaryparser;
/**
* Represents a parsed float.
*/
public final class Float64 extends ParsedType<Double> {
/**
* Creates a new float object.
*
* @param bytePosition Byte position of the float in the input stream.
* @param value Float value.
*/
public Float64(final int bytePosition, final Double value) {
super(bytePosition, 64, value);
}
}

View File

@ -0,0 +1,21 @@
package tv.porst.splib.binaryparser;
/**
* Interface to be implemented by all parsed types.
*/
public interface IFileElement {
/**
* Returns the length of the element in bits.
*
* @return The length of the element in bits.
*/
int getBitLength();
/**
* Returns the position of the element in bits.
*
* @return The position of the element in bits.
*/
int getBitPosition();
}

View File

@ -0,0 +1,17 @@
package tv.porst.splib.binaryparser;
/**
* Represents a parsed short int.
*/
public final class INT16 extends ParsedType<Integer> {
/**
* Creates a new short int object.
*
* @param bytePosition Byte position of the short int in the input stream.
* @param value Integer value.
*/
public INT16(final int bytePosition, final int value) {
super(bytePosition, 16, value);
}
}

View File

@ -0,0 +1,17 @@
package tv.porst.splib.binaryparser;
/**
* Represents a parsed 3-byte int.
*/
public final class INT24 extends ParsedType<Integer> {
/**
* Creates a new 3-byte int object.
*
* @param bytePosition Byte position of the 3-byte int in the input stream.
* @param value Integer value.
*/
public INT24(final int bytePosition, final int value) {
super(bytePosition, 24, value);
}
}

View File

@ -0,0 +1,22 @@
package tv.porst.splib.binaryparser;
/**
* Represents a parsed int.
*/
public final class INT32 extends ParsedType<Integer> implements IParsedINTElement {
/**
* Length of the type in bytes.
*/
public static final int BYTE_LENGTH = 4;
/**
* Creates a new short int object.
*
* @param bytePosition Byte position of the int in the input stream.
* @param value Integer value.
*/
public INT32(final int bytePosition, final int value) {
super(bytePosition, 32, value);
}
}

View File

@ -0,0 +1,17 @@
package tv.porst.splib.binaryparser;
/**
* Represents a parsed long.
*/
public final class INT64 extends ParsedType<Long> implements IParsedINTElement {
/**
* Creates a new long object.
*
* @param bytePosition Byte position of the long in the input stream.
* @param value Long value.
*/
public INT64(final int bytePosition, final long value) {
super(bytePosition, 64, value);
}
}

View File

@ -0,0 +1,8 @@
package tv.porst.splib.binaryparser;
/**
* Interface to be implemented by parsed integer values.
*/
public interface IParsedINTElement extends IFileElement {
}

View File

@ -0,0 +1,70 @@
package tv.porst.splib.binaryparser;
/**
* Base class for all parsed types.
*
* @param <ValueType> Type of the parsed value.
*/
public abstract class ParsedType<ValueType> implements IFileElement {
/**
* Bit position of the parsed value in the input stream.
*/
private final int bitPosition;
/**
* Parsed value.
*/
private final ValueType value;
/**
* Length of the field in bits.
*/
private final int bitLength;
/**
* Creates a new parsed type object.
*
* @param bitPosition Bit position of the parsed value in the input stream.
* @param bitLength Length of the field in bits.
* @param value Parsed value.
*/
public ParsedType(final int bitPosition, final int bitLength, final ValueType value) {
if (bitPosition < 0) {
throw new IllegalArgumentException("Byte position must not be negative");
}
if (value == null) {
throw new IllegalArgumentException("Value argument must not be null");
}
this.bitPosition = bitPosition;
this.bitLength = bitLength;
this.value = value;
}
@Override
public int getBitLength() {
return bitLength;
}
/**
* Returns the byte position of the parsed value in the input stream.
*
* @return The byte position of the parsed value in the input stream.
*/
@Override
public int getBitPosition() {
return bitPosition;
}
/**
* Returns the parsed value.
*
* @return The parsed value.
*/
public ValueType value() {
return value;
}
}

View File

@ -0,0 +1,18 @@
package tv.porst.splib.binaryparser;
/**
* Represents a parsed unsigned bit field.
*/
public final class UBits extends ParsedType<Integer> {
/**
* Creates a new unsigned bit field object.
*
* @param bitPosition Bit position of the unsigned bit field in the input stream.
* @param bitLength Length of the field in bits.
* @param value Unsigned bit field value.
*/
public UBits(final int bitPosition, final int bitLength, final Integer value) {
super(bitPosition, bitLength, value);
}
}

View File

@ -0,0 +1,22 @@
package tv.porst.splib.binaryparser;
/**
* Represents a parsed unsigned short int.
*/
public final class UINT16 extends ParsedType<Integer> implements IParsedINTElement {
/**
* Length of the type in bytes.
*/
public static final int BYTE_LENGTH = 2;
/**
* Creates a new unsigned short int object.
*
* @param bytePosition Byte position of the unsigned short int in the input stream.
* @param value Integer value.
*/
public UINT16(final int bytePosition, final int value) {
super(bytePosition, 16, value);
}
}

View File

@ -0,0 +1,22 @@
package tv.porst.splib.binaryparser;
/**
* Represents a parsed unsigned int.
*/
public class UINT32 extends ParsedType<Long> implements IParsedINTElement {
/**
* Length of the type in bytes.
*/
public static final int BYTE_LENGTH = 4;
/**
* Creates a new unsigned int object.
*
* @param bytePosition Byte position of the unsigned int in the input stream.
* @param value Integer value.
*/
public UINT32(final int bytePosition, final long value) {
super(bytePosition, 32, value);
}
}

View File

@ -0,0 +1,22 @@
package tv.porst.splib.binaryparser;
/**
* Represents a parsed unsigned byte.
*/
public final class UINT8 extends ParsedType<Integer> implements IParsedINTElement {
/**
* Length of the type in bytes.
*/
public static final int BYTE_LENGTH = 1;
/**
* Creates a new unsigned byte object.
*
* @param bytePosition Byte position of the unsigned byte in the input stream.
* @param value Integer value.
*/
public UINT8(final int bytePosition, final int value) {
super(bytePosition, 8, value);
}
}

View File

@ -0,0 +1,7 @@
package tv.porst.splib.binaryparser;
/**
* This package contains classes for parsing binary data. The main class
* of this package is {@link BinaryParser}. Using this class you can parse
* binary data into the types defined in the other classes.
*/

View File

@ -0,0 +1,51 @@
package tv.porst.splib.convert;
import java.awt.event.KeyEvent;
/**
* Converts helper functions for working with different data types.
*/
public final class ConvertHelpers {
/**
* Tests whether a given character is a valid decimal character.
*
* @param c The character to test.
*
* @return True, if the given character is a valid decimal character.
*/
public static boolean isDecCharacter(final char c)
{
return c >= '0' && c <= '9';
}
/**
* Tests whether a character is a valid character of a hexadecimal string.
*
* @param c The character to test.
*
* @return True, if the character is a hex character. False, otherwise.
*/
public static boolean isHexCharacter(final char c)
{
return isDecCharacter(c) || c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z';
}
/**
* Tests whether a character is a printable ASCII character.
*
* @param c The character to test.
*
* @return True, if the character is a printable ASCII character. False,
* otherwise.
*/
public static boolean isPrintableCharacter(final char c)
{
final Character.UnicodeBlock block = Character.UnicodeBlock.of(c);
return !Character.isISOControl(c) &&
c != KeyEvent.CHAR_UNDEFINED &&
block != null &&
block != Character.UnicodeBlock.SPECIALS;
}
}

View File

@ -0,0 +1,5 @@
package tv.porst.splib.convert;
/**
* This package contains classes for converting between types.
*/

View File

@ -0,0 +1,33 @@
package tv.porst.splib.file;
import java.io.File;
/**
* Class for recursive directory traversal.
*/
public final class DirectoryTraverser {
/**
* Traverses a directory.
*
* @param directory The directory to traverse.
* @param callback The callback object to invoked for each encountered file.
*/
public static void traverse(final File directory, final IDirectoryTraversalVisitor callback) {
if (directory.isDirectory()) {
for (final File file : directory.listFiles()) {
if (!file.isDirectory()) {
callback.visit(file);
}
}
for (final File file : directory.listFiles()) {
if (file.isDirectory()) {
traverse(file, callback);
}
}
}
}
}

View File

@ -0,0 +1,111 @@
package tv.porst.splib.file;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* Contains helper functions for working with files.
*/
public final class FileHelpers {
/**
* Extracts the filename from a full path.
*
* @param fullPath The full input path.
*
* @return The raw filename.
*/
public static String extractFilename(final String fullPath) {
final int index = fullPath.lastIndexOf(File.separator);
return index == -1 ? fullPath : fullPath.substring(index + 1);
}
/**
* Reads a whole file into a byte array.
*
* @param file The input file.
*
* @return The bytes read from the file.
*
* @throws IOException Thrown if the file could not be read.
*/
public static byte[] readFile(final File file) throws IOException {
if (file == null) {
throw new IllegalArgumentException("File argument must not be null");
}
final FileInputStream inputStream = new FileInputStream(file);
final byte[] data = new byte[(int) file.length()];
inputStream.read(data);
return data;
}
/**
* Reads a whole file into a byte array.
*
* @param file The input file.
*
* @return The bytes read from the file.
*
* @throws IOException Thrown if the file could not be read.
*/
public static byte[] readFile(final String file) throws IOException {
if (file == null) {
throw new IllegalArgumentException("File argument must not be null");
}
return readFile(new File(file));
}
/**
* Writes binary data to a file.
*
* @param file The file to write to.
* @param data The data to write.
*
* @throws IOException Thrown if writing the data to the byte failed.
*/
public static void writeFile(final File file, final byte[] data) throws IOException {
if (file == null) {
throw new IllegalArgumentException("File argument must not be null");
}
final FileOutputStream inputStream = new FileOutputStream(file);
inputStream.write(data);
inputStream.close();
}
/**
* Writes binary data to a file.
*
* @param fileName The name of the file to write to.
* @param data The data to write.
*
* @throws IOException Thrown if writing the data to the byte failed.
*/
public static void writeFile(final String fileName, final byte[] data) throws IOException {
if (fileName == null) {
throw new IllegalArgumentException("File name argument must not be null");
}
final File file = new File(fileName);
final FileOutputStream inputStream = new FileOutputStream(file);
inputStream.write(data);
inputStream.close();
}
}

View File

@ -0,0 +1,16 @@
package tv.porst.splib.file;
import java.io.File;
/**
* Interface to be implemented by all callback objects for directory traversal.
*/
public interface IDirectoryTraversalVisitor {
/**
* Invoked when a new file is found during traversal.
*
* @param file The next file.
*/
void visit(File file);
}

View File

@ -0,0 +1,5 @@
package tv.porst.splib.file;
/**
* This package contains classes for working with files.
*/

View File

@ -0,0 +1,41 @@
package tv.porst.splib.general;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* Class that provides a generic interface for working with listeners.
*
* @param <ListenerType> Type of the listeners managed by the class.
*/
public final class ListenerProvider<ListenerType> implements Iterable<ListenerType> {
/**
* Currently added listeners.
*/
private final List<ListenerType> listeners = new ArrayList<ListenerType>();
/**
* Adds a new listener to the provider.
*
* @param listener The new listener object to add.
*/
public void add(final ListenerType listener) {
listeners.add(listener);
}
@Override
public Iterator<ListenerType> iterator() {
return listeners.iterator();
}
/**
* Removes a listener from the provider.
*
* @param listener The listener to remove.
*/
public void remove(final ListenerType listener) {
listeners.remove(listener);
}
}

View File

@ -0,0 +1,5 @@
package tv.porst.splib.general;
/**
* This package contains classes that do not fit into any of the other packages.
*/

View File

@ -0,0 +1,27 @@
package tv.porst.splib.gui;
import java.awt.Font;
import java.awt.GraphicsEnvironment;
/**
* Helper class that contains GUI functions.
*/
public final class GuiHelpers {
/**
* Returns the system monospace font.
*
* @return The name of the system monospace font.
*/
public static String getMonospaceFont() {
final GraphicsEnvironment localGraphicsEnvironment = GraphicsEnvironment.getLocalGraphicsEnvironment();
final Font[] fonts = localGraphicsEnvironment.getAllFonts();
for (final Font font : fonts) {
if (font.getName().equals("Courier New")) {
return "Courier New";
}
}
return "Monospaced";
}
}

View File

@ -0,0 +1,5 @@
package tv.porst.splib.gui;
/**
* This package contains classes for working with GUI components.
*/

View File

@ -0,0 +1,15 @@
package tv.porst.splib.gui.caret;
/**
* Interface to be implemented by classes that want to be notified about
* changed in the caret.
*/
public interface ICaretListener
{
/**
* Invoked after the caret status changed.
*
* @param caret The caret whose status changed.
*/
public void caretStatusChanged(JCaret caret);
}

View File

@ -0,0 +1,245 @@
package tv.porst.splib.gui.caret;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.Timer;
/**
* Caret class that can be used to draw carets in custom GUI components.
*/
public class JCaret
{
/**
* The default blink time of the caret in milliseconds.
*/
private static final int DEFAULT_BLINK_TIME = 500;
/**
* The default color of the caret.
*/
private static final Color DEFAULT_CARET_COLOR = Color.RED;
/**
* List of listeners that are notified when the caret status changes.
*/
private final List<ICaretListener> m_listeners = new ArrayList<ICaretListener>();
/**
* Timer that is used to make the caret blink.
*/
private final Timer m_caretTimer;
/**
* Flag that determines whether the caret is visible or not.
*/
private boolean m_isCaretVisible = false;
/**
* The color of the caret.
*/
private final Color m_caretColor = Color.RED;
/**
* Listeners that are notified about changes in the caret.
*/
private final InternalListener m_listener = new InternalListener();
/**
* Creates a new caret with a default blink period of 500ms and
* the default caret color red.
*/
public JCaret()
{
this(DEFAULT_BLINK_TIME, DEFAULT_CARET_COLOR);
}
/**
* Creates a new caret with the default blink period and a custom caret color.
*
* @param caretColor The color of the caret.
*
* @throws NullPointerException Thrown if the color is null.
*/
public JCaret(final Color caretColor)
{
this(DEFAULT_BLINK_TIME, caretColor);
}
/**
* Creates a new caret with a custom blink period and the default caret color red.
*
* @param blinkPeriod The blink period in milliseconds.
*
* @throws IllegalArgumentException Thrown if the blink period is negative.
*/
public JCaret(final int blinkPeriod)
{
this(blinkPeriod, DEFAULT_CARET_COLOR);
}
/**
* Creates a new caret with a custom blink period and a custom
* caret color.
*
* @param blinkPeriod The blink period in milliseconds.
* @param caretColor The color of the caret.
* @throws IllegalArgumentException Thrown if the blink period is negative.
* @throws NullPointerException Thrown if the color is null.
*/
public JCaret(final int blinkPeriod, final Color caretColor)
{
if (blinkPeriod < 0)
{
throw new IllegalArgumentException("Error: Blink period can't be negative");
}
if (caretColor == null)
{
throw new NullPointerException("Error: Caret color can't be null");
}
// Initialize the timer that makes the caret blink
m_caretTimer = new Timer(blinkPeriod, m_listener);
m_caretTimer.setRepeats(true);
m_caretTimer.start();
}
/**
* Notifies all listeners of a status change of the caret.
*/
private void notifyListeners()
{
for (final ICaretListener listener : m_listeners)
{
listener.caretStatusChanged(JCaret.this);
}
}
/**
* Adds a new status change listener to the list of listeners.
*
* @param listener The new listener.
*
* @throws NullPointerException Thrown if the passed listener is null.
*/
public void addCaretListener(final ICaretListener listener)
{
if (listener == null)
{
throw new NullPointerException("Error: Listener can't be null");
}
// No duplicate listeners
if (!m_listeners.contains(listener))
{
m_listeners.add(listener);
}
}
/**
* Draws the caret on a given graphics surface.
*
* @param g The graphics surface where the caret is drawn.
* @param x The x coordinate of the caret.
* @param y The y coordinate of the caret.
* @param height The height of the caret.
*
* @throws NullPointerException Thrown if the graphics context is null.
*/
public void draw(final Graphics g, final int x, final int y, final int height)
{
if (g == null)
{
throw new NullPointerException("Error: Graphics context can't be null");
}
if (isVisible())
{
// Save the old color.
final Color oldColor = g.getColor();
// Draw the caret
g.setColor(m_caretColor);
g.drawLine(x, y, x, y + height - 1);
// Restore the old color.
g.setColor(oldColor);
}
}
/**
* Determines whether the caret is currently visible nor not.
*
* @return True, if the caret is visible. False, otherwise.
*/
public boolean isVisible()
{
return m_isCaretVisible;
}
/**
* Removes a registered listeners.
*
* @param listener The listener to remove.
*/
public void removeListener(final ICaretListener listener)
{
m_listeners.remove(listener);
}
/**
* Sets the visibility status of the caret.
*
* @param isCaretVisible The new visibility status of the caret.
*/
public void setVisible(final boolean isCaretVisible)
{
this.m_isCaretVisible = isCaretVisible;
notifyListeners();
}
/**
* Stops the caret from blinking.
*/
public void stop()
{
m_caretTimer.stop();
m_caretTimer.removeActionListener(m_listener);
setVisible(false);
}
/**
* Class of internal listeners that are used to hide
* the callback functions from the public interface.
*/
private class InternalListener implements ActionListener
{
/**
* This function is called every time the caret timer
* ticks.
*/
@Override
public void actionPerformed(final ActionEvent arg0)
{
// Switch the caret status at every timer click.
m_isCaretVisible = !m_isCaretVisible;
notifyListeners();
}
}
}

View File

@ -0,0 +1,5 @@
package tv.porst.splib.gui.caret;
/**
* This package contains classes that simulate a caret in custom components.
*/

View File

@ -0,0 +1,76 @@
package tv.porst.splib.gui.tree;
import javax.swing.Icon;
import javax.swing.tree.DefaultMutableTreeNode;
/**
* Represents a node in an icon tree.
*/
public class IconNode extends DefaultMutableTreeNode {
/**
* The icon shown for the node.
*/
protected Icon icon;
/**
* Name of the icon.
*/
protected String iconName;
/**
* Creates a new icon node.
*/
public IconNode() {
this(null);
}
/**
* Creates a new icon node.
*
* @param userObject The user object associated with the node.
*/
public IconNode(final Object userObject) {
this(userObject, true, null);
}
/**
* Creates a new icon node.
*
* @param userObject The user object associated with the node.
* @param allowsChildren Flag that says whether the node can have children or not.
* @param icon The icon shown in the node.
*/
public IconNode(final Object userObject, final boolean allowsChildren, final Icon icon) {
super(userObject, allowsChildren);
this.icon = icon;
}
/**
* Returns the icon of the node.
*
* @return The icon of the node.
*/
public Icon getIcon() {
return icon;
}
/**
* Returns the icon name of the node.
*
* @return The icon name of the node.
*/
public String getIconName() {
if (iconName != null) {
return iconName;
} else {
final String str = userObject.toString();
int index = str.lastIndexOf(".");
if (index != -1) {
return str.substring(++index);
} else {
return null;
}
}
}
}

View File

@ -0,0 +1,63 @@
package tv.porst.splib.gui.tree;
import java.awt.Component;
import javax.swing.JTree;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.TreeModel;
/**
* Renderer used to display icons in tree nodes.
*/
class CustomTreeCellRenderer extends DefaultTreeCellRenderer {
@Override
public Component getTreeCellRendererComponent(final JTree tree, final Object value, final boolean sel, final boolean expanded, final boolean leaf, final int row, final boolean hasFocus) {
if((value instanceof IconNode) && (value != null)) {
setIcon(((IconNode)value).getIcon());
}
//we can not call super.getTreeCellRendererComponent method, since it overrides our setIcon call and cause rendering of labels to '...' when node expansion is done
//so, we copy (and modify logic little bit) from super class method:
final String stringValue = tree.convertValueToText(value, sel,
expanded, leaf, row, hasFocus);
this.hasFocus = hasFocus;
setText(stringValue);
if(sel) {
setForeground(getTextSelectionColor());
} else {
setForeground(getTextNonSelectionColor());
}
if (!tree.isEnabled()) {
setEnabled(false);
}
else {
setEnabled(true);
}
setComponentOrientation(tree.getComponentOrientation());
selected = sel;
return this;
}
}
/**
* Represents a tree where the nodes are custom icons.
*/
public class IconTree extends JTree {
/**
* Creates a new icon tree object.
*
* @param model The model of the icon tree.
*/
public IconTree(final TreeModel model) {
super(model);
setCellRenderer(new CustomTreeCellRenderer());
}
}

View File

@ -0,0 +1,5 @@
package tv.porst.splib.gui.tree;
/**
* This package contains classes for working with trees.
*/

View File

@ -0,0 +1,41 @@
package tv.porst.splib.maps;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* Contains helper functions for working with maps.
*/
public final class MapHelpers {
/**
* Sorts a map by its values.
*
* @param <S> Type of map keys.
* @param <T> Type of map values.
* @param map The map to sort.
*
* @return The sorted map.
*/
public static <S, T extends Comparable<T>> Map<S, T> sortByValue(final Map<S, T> map) {
final List<Map.Entry<S, T>> list = new LinkedList<Map.Entry<S, T>>(map.entrySet());
Collections.sort(list, new Comparator<Map.Entry<S, T>>() {
@Override
public int compare(final Map.Entry<S, T> o1, final Map.Entry<S, T> o2) {
return o1.getValue().compareTo(o2.getValue());
}
});
final Map<S, T> result = new LinkedHashMap<S, T>();
for (final Map.Entry<S, T> element : list) {
result.put(element.getKey(), element.getValue());
}
return result;
}
}

View File

@ -0,0 +1,5 @@
package tv.porst.splib.maps;
/**
* This package contains classes for working with maps.
*/

View File

@ -0,0 +1,60 @@
package tv.porst.splib.strings;
/**
* Contains helper functions for working with strings.
*/
public final class StringHelpers {
/**
* Joins a bunch of strings into a single string.
*
* @param parts The string parts to join.
* @param delimiter The delimiter to add between strings.
*
* @return The joined string.
*/
public static String join(final String[] parts, final String delimiter) {
final StringBuilder builder = new StringBuilder();
for (final String part : parts) {
if (part == parts[0] && (part == null || part.equals(""))) {
continue;
}
if (builder.length() != 0) {
builder.append(delimiter);
}
builder.append(part);
}
return builder.toString();
}
/**
* Creates a new string by repeating a given string a number of times.
*
* @param string The string to repeat.
* @param repeats Number of repeats.
*
* @return The new string.
*/
public static String repeat(final String string, final int repeats) {
if (repeats < 0) {
throw new IllegalArgumentException("Number of repeats can not be null");
}
if (repeats == 0) {
return "";
}
final StringBuilder builder = new StringBuilder();
for (int i = 0; i < repeats; i++) {
builder.append(string);
}
return builder.toString();
}
}

View File

@ -0,0 +1,5 @@
package tv.porst.splib.strings;
/**
* This package contains classes for working with strings.
*/

5
build.gradle Normal file
View File

@ -0,0 +1,5 @@
subprojects {
tasks.withType(JavaCompile) {
options.encoding = 'UTF-8'
}
}

View File

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

185
gradlew vendored Normal file
View File

@ -0,0 +1,185 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"

89
gradlew.bat vendored Normal file
View File

@ -0,0 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

2
settings.gradle Normal file
View File

@ -0,0 +1,2 @@
rootProject.name = 'brainfuck-ide'
include ':bfIDE', ':BFInterpreter'