@ -0,0 +1,14 @@ |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<classpath> |
||||
<classpathentry kind="src" path="src"/> |
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/> |
||||
<classpathentry kind="lib" path="lib/TableLayout-bin-jdk1.5-2009-08-26.jar"/> |
||||
<classpathentry kind="lib" path="lib/jspeex.jar"> |
||||
<attributes> |
||||
<attribute name="javadoc_location" value="http://jspeex.sourceforge.net/doc/"/> |
||||
</attributes> |
||||
</classpathentry> |
||||
<classpathentry kind="lib" path="lib/jtablet-2010-04-13-1.jar"/> |
||||
<classpathentry kind="lib" path="lib/jtablet2-thin-2010-04-13-1.jar"/> |
||||
<classpathentry kind="output" path="bin"/> |
||||
</classpath> |
@ -0,0 +1,8 @@ |
||||
**/.DS_Store |
||||
**/*.p12 |
||||
.settings |
||||
.project |
||||
|
||||
bin/ |
||||
|
||||
obfuscate-sketcher1.xml |
@ -0,0 +1,42 @@ |
||||
<project name="Sketcher2" default="dist" basedir="."> |
||||
<taskdef resource="proguard_task.properties" classpath="lib:lib/proguard.jar" /> |
||||
<target name="dist"> |
||||
<tstamp/> |
||||
|
||||
<jar destfile="sketcher2-server-${DSTAMP}-${TSTAMP}.jar" basedir="bin" includes="com/jotuntech/sketcher/common/**,com/jotuntech/sketcher/server/**"/> |
||||
<jar destfile="sketcher2-client-internal-${DSTAMP}-${TSTAMP}.jar" basedir="bin" includes="com/jotuntech/sketcher/common/**,com/jotuntech/sketcher/client/**" excludes="com/jotuntech/sketcher/client/audio/**,com/jotuntech/sketcher/client/images/**"/> |
||||
<jar destfile="sketcher2-client-resources-${DSTAMP}-${TSTAMP}.jar" basedir="bin" includes="com/jotuntech/sketcher/client/audio/**,com/jotuntech/sketcher/client/images/**"/> |
||||
|
||||
<proguard> |
||||
-libraryjars "${java.home}/lib/rt.jar" |
||||
-libraryjars lib/ |
||||
-injars sketcher2-client-internal-${DSTAMP}-${TSTAMP}.jar |
||||
-outjars sketcher2-client-${DSTAMP}-${TSTAMP}.jar |
||||
-printmapping sketcher2-client-${DSTAMP}-${TSTAMP}.map |
||||
-keep public class * extends java.applet.Applet |
||||
-keep public class * extends com.jotuntech.sketcher.client.Command |
||||
-keep public class * extends com.jotuntech.sketcher.common.filter.Filter |
||||
</proguard> |
||||
|
||||
<property name="certificate.keystore" location="jotun.p12"/> |
||||
<property name="certificate.storetype" value="pkcs12"/> |
||||
<property name="certificate.alias" value="le-20ab1c70-a64e-486b-ac42-a0f8ac0941f9"/> |
||||
<property name="certificate.password" value="rweysgib"/> |
||||
|
||||
<signjar jar="sketcher2-client-${DSTAMP}-${TSTAMP}.jar" alias="${certificate.alias}" storepass="${certificate.password}" keystore="${certificate.keystore}" storetype="${certificate.storetype}" verbose="true"/> |
||||
<signjar jar="sketcher2-client-resources-${DSTAMP}-${TSTAMP}.jar" alias="${certificate.alias}" storepass="${certificate.password}" keystore="${certificate.keystore}" storetype="${certificate.storetype}" verbose="true"/> |
||||
<!-- |
||||
<signjar jar="lib/jtablet2-thin-2010-04-13-1.jar" alias="${certificate.alias}" storepass="${certificate.password}" keystore="${certificate.keystore}" storetype="${certificate.storetype}" verbose="true"/> |
||||
<signjar jar="lib/jtablet-2010-04-13-1.jar" alias="${certificate.alias}" storepass="${certificate.password}" keystore="${certificate.keystore}" storetype="${certificate.storetype}" verbose="true"/> |
||||
<signjar jar="lib/jspeex.jar" alias="${certificate.alias}" storepass="${certificate.password}" keystore="${certificate.keystore}" storetype="${certificate.storetype}" verbose="true"/> |
||||
<signjar jar="lib/mail.jar" alias="${certificate.alias}" storepass="${certificate.password}" keystore="${certificate.keystore}" storetype="${certificate.storetype}" verbose="true"/> |
||||
<signjar jar="lib/TableLayout-bin-jdk1.5-2009-08-26.jar" alias="${certificate.alias}" storepass="${certificate.password}" keystore="${certificate.keystore}" storetype="${certificate.storetype}" verbose="true"/> |
||||
--> |
||||
<delete file="sketcher2-client-internal-${DSTAMP}-${TSTAMP}.jar"/> |
||||
<copy file="sketcher2-client-${DSTAMP}-${TSTAMP}.jar" tofile="../ArtGrounds2/resources/sketcher2-client.jar"/> |
||||
<copy file="sketcher2-client-resources-${DSTAMP}-${TSTAMP}.jar" tofile="../ArtGrounds2/resources/sketcher2-client-resources.jar"/> |
||||
<copy file="sketcher2-client-resources-${DSTAMP}-${TSTAMP}.jar" tofile="sketcher2-client-resources.jar"/> |
||||
<copy file="sketcher2-server-${DSTAMP}-${TSTAMP}.jar" tofile="../ArtGrounds2/lib/sketcher2-server.jar"/> |
||||
<copy file="sketcher2-server-${DSTAMP}-${TSTAMP}.jar" tofile="../JotunWebsite/lib/sketcher2-server.jar"/> |
||||
</target> |
||||
</project> |
@ -0,0 +1,2 @@ |
||||
proguard = proguard.ant.ProGuardTask |
||||
proguardconfiguration = proguard.ant.ConfigurationTask |
@ -0,0 +1,626 @@ |
||||
package com.jotuntech.sketcher.client; |
||||
|
||||
import java.io.File; |
||||
import java.io.FileInputStream; |
||||
import java.io.FileOutputStream; |
||||
import java.io.IOException; |
||||
import java.net.InetSocketAddress; |
||||
import java.nio.ByteBuffer; |
||||
import java.nio.channels.SocketChannel; |
||||
import java.util.Properties; |
||||
import java.util.Timer; |
||||
import java.util.TimerTask; |
||||
import java.util.concurrent.ArrayBlockingQueue; |
||||
import java.util.concurrent.BlockingQueue; |
||||
import java.util.concurrent.TimeUnit; |
||||
|
||||
import javax.swing.event.ChangeEvent; |
||||
import javax.swing.event.ChangeListener; |
||||
|
||||
import com.jotuntech.sketcher.client.command.PingCommand; |
||||
import com.jotuntech.sketcher.client.command.SignInCommand; |
||||
import com.jotuntech.sketcher.client.command.SignOutCommand; |
||||
import com.jotuntech.sketcher.client.voice.VoiceClient; |
||||
import com.jotuntech.sketcher.client.voice.VoiceEvent; |
||||
import com.jotuntech.sketcher.client.voice.VoiceListener; |
||||
import com.jotuntech.sketcher.common.Brush; |
||||
import com.jotuntech.sketcher.common.Canvas; |
||||
import com.jotuntech.sketcher.common.Layer; |
||||
import com.jotuntech.sketcher.common.Log; |
||||
import com.jotuntech.sketcher.common.StreamableUtils; |
||||
import com.jotuntech.sketcher.common.TwoWayHashMap; |
||||
import com.jotuntech.sketcher.common.User; |
||||
|
||||
/** |
||||
* @author Thor Harald Johansen |
||||
* |
||||
*/ |
||||
|
||||
public class Client extends Thread { |
||||
private UserInterface userInterface; |
||||
private TwoWayHashMap<Integer, User> userMap; |
||||
private User[] userArray; |
||||
private Canvas canvas; |
||||
private long lastLayerClean; |
||||
private Connection connection; |
||||
private BlockingQueue<CommandEntry> commandQueue; |
||||
private ByteBuffer commandBuffer; |
||||
private String login; |
||||
private String password; |
||||
private String hostname; |
||||
private int port; |
||||
private VoiceClient voiceClient; |
||||
private boolean soundEnabled; |
||||
private boolean ads; |
||||
|
||||
//private Random sendRandom = new Random(0x707733360A596E0AL);
|
||||
//private Random recvRandom = new Random(0x59A70A401C8CD1EAL);
|
||||
|
||||
private Properties props; |
||||
private File propsFile; |
||||
Timer propsTimer = new Timer("PropsTimer", true); |
||||
|
||||
private Brush defaultBrushes[] = new Brush[] { |
||||
new Brush("Pen", 255, 255, 3.375f, 1, 0.27f, true, false, true, 0, 0, 0, false, 0.5f), |
||||
new Brush("Pencil", 255, 255, 2f, 2, 0.45f, true, false, true, 1.5f, 0, 0, false, 0.5f), |
||||
new Brush("Water", 255, 255, 16f, 4, 0.22f, true, false, true, 0, 0, 128, false, 0.5f), |
||||
new Brush("Eraser", -255, 192, 16f, 4, 0.2f, false, true, false, 0, 0, 0, false, 0.5f), |
||||
new Brush("Ink", 255, 255, 4f, 1, 0.04f, false, false, true, 0, 0, 0, false, 0.5f), |
||||
new Brush("Wipe", -255, 255, 64f, 7, 0.2f, false, false, false, 0, 0, 0, false, 0.5f) |
||||
}; |
||||
|
||||
protected Brush[] brushes = new Brush[defaultBrushes.length]; |
||||
|
||||
public Client(String hostname, int port, String login, String password, boolean ads) { |
||||
super("ClientThread"); |
||||
|
||||
/** Save hostname and port */ |
||||
this.hostname = hostname; |
||||
this.port = port; |
||||
|
||||
/** Save login and password */ |
||||
this.login = login; |
||||
this.password = password; |
||||
|
||||
/** Save ad setting */ |
||||
this.ads = ads; |
||||
|
||||
/** Create user map & array */ |
||||
userMap = new TwoWayHashMap<Integer, User>(); |
||||
userMap.addChangeListener(new ChangeListener() { |
||||
public void stateChanged(ChangeEvent arg0) { |
||||
/** Populate user array in a thread-safe way */ |
||||
User[] newUserArray = new User[0]; |
||||
newUserArray = userMap.values().toArray(newUserArray); |
||||
userArray = newUserArray; |
||||
} |
||||
}); |
||||
|
||||
/** Create user array */ |
||||
userArray = new User[0]; |
||||
|
||||
/** Create command queue */ |
||||
commandQueue = new ArrayBlockingQueue<CommandEntry>(65536, true); |
||||
|
||||
for(int i = 0; i < brushes.length; i++) { |
||||
brushes[i] = defaultBrushes[i].copy(); |
||||
} |
||||
|
||||
/** Create user interface */ |
||||
userInterface = new UserInterface(this); |
||||
|
||||
/** Load properties file */ |
||||
try { |
||||
propsFile = new File(System.getProperty("user.home"), "sketcher.properties"); |
||||
} catch(SecurityException e) { |
||||
propsFile = null; |
||||
} |
||||
|
||||
loadProps(); |
||||
|
||||
/** Set last layer cleanup time */ |
||||
lastLayerClean = System.currentTimeMillis(); |
||||
|
||||
propsTimer.schedule(new TimerTask() { |
||||
public void run() { |
||||
saveProps(); |
||||
} |
||||
}, 60000, 60000); |
||||
} |
||||
|
||||
synchronized public void resetProps() { |
||||
if(propsFile != null) { |
||||
propsFile.delete(); |
||||
} |
||||
loadProps(); |
||||
} |
||||
|
||||
synchronized private void loadProps() { |
||||
props = new Properties(); |
||||
|
||||
if(propsFile != null) { |
||||
try { |
||||
if(propsFile.exists()) { |
||||
FileInputStream is = new FileInputStream(propsFile); |
||||
props.load(is); |
||||
is.close(); |
||||
} |
||||
} catch(IOException e) { |
||||
|
||||
} catch(SecurityException e) { |
||||
|
||||
} |
||||
} |
||||
|
||||
int propsVersion = Integer.valueOf(props.getProperty("file.version", "0")); |
||||
|
||||
soundEnabled = "true".equals(props.getProperty("sound.enabled", "true")); |
||||
userInterface.getSoundItem().setSelected(soundEnabled); |
||||
|
||||
userInterface.setSmoothZoom("true".equals(props.getProperty("zoom.smooth", "true"))); |
||||
|
||||
userInterface.getEditor().setTagsEnabled("true".equals(props.getProperty("tags.enabled", "true"))); |
||||
userInterface.getEditor().setSoftwareCursorEnabled("true".equals(props.getProperty("cursor.software", "false"))); |
||||
|
||||
userInterface.getTagsItem().setSelected(userInterface.getEditor().isTagsEnabled()); |
||||
|
||||
for(JCollapsiblePanel panel : userInterface.getToolPanels()) { |
||||
panel.setExpanded("true".equals(props.getProperty("panel" + panel.getId() + ".expanded", panel.isExpandedByDefault() ? "true" : "false")), false); |
||||
panel.setStuck("true".equals(props.getProperty("panel" + panel.getId() + ".stuck", panel.isStuckByDefault() ? "true" : "false"))); |
||||
} |
||||
|
||||
for(int i = 0; i < brushes.length; i++) { |
||||
brushes[i] = defaultBrushes[i].copy(); |
||||
} |
||||
|
||||
for(int i = 0; i < defaultBrushes.length; i++) { |
||||
String name = props.getProperty("brush" + i + ".name"); |
||||
String opacity = props.getProperty("brush" + i + ".opacity"); |
||||
String flow = props.getProperty("brush" + i + ".flow"); |
||||
String radius = props.getProperty("brush" + i + ".radius"); |
||||
String hardness = props.getProperty("brush" + i + ".hardness"); |
||||
String spacing = props.getProperty("brush" + i + ".spacing"); |
||||
String pressureToFlow, pressureToOpacity; |
||||
if(propsVersion < 1) { |
||||
pressureToFlow = props.getProperty("brush" + i + ".pressureToOpacity"); |
||||
pressureToOpacity = props.getProperty("brush" + i + ".realPressureToOpacity"); |
||||
} else { |
||||
pressureToFlow = props.getProperty("brush" + i + ".pressureToFlow"); |
||||
pressureToOpacity = props.getProperty("brush" + i + ".pressureToOpacity"); |
||||
} |
||||
String pressureToRadius = props.getProperty("brush" + i + ".pressureToRadius"); |
||||
String jitter = props.getProperty("brush" + i + ".jitter"); |
||||
String water = props.getProperty("brush" + i + ".water"); |
||||
String waterArea = props.getProperty("brush" + i + ".waterArea"); |
||||
String lockTransparency = props.getProperty("brush" + i + ".lockTransparency"); |
||||
|
||||
Brush b = brushes[i]; |
||||
if(name != null) { |
||||
b.setName(name); |
||||
} |
||||
if(opacity != null) { |
||||
b.setOpacity(Integer.valueOf(opacity)); |
||||
} |
||||
if(flow != null) { |
||||
b.setFlow(Integer.valueOf(flow)); |
||||
} |
||||
if(radius != null) { |
||||
b.setRadius(Float.valueOf(radius)); |
||||
} |
||||
if(hardness != null) { |
||||
b.setHardness(Integer.valueOf(hardness)); |
||||
} |
||||
if(spacing != null) { |
||||
b.setSpacing(Float.valueOf(spacing)); |
||||
} |
||||
if(pressureToOpacity != null) { |
||||
b.setPressureToOpacity("true".equals(pressureToOpacity)); |
||||
} |
||||
if(pressureToFlow != null) { |
||||
b.setPressureToFlow("true".equals(pressureToFlow)); |
||||
} |
||||
if(pressureToRadius != null) { |
||||
b.setPressureToRadius("true".equals(pressureToRadius)); |
||||
} |
||||
if(jitter != null) { |
||||
b.setJitter(Float.valueOf(jitter)); |
||||
} |
||||
if(water != null) { |
||||
b.setWater(Integer.valueOf(water)); |
||||
} |
||||
if(waterArea != null) { |
||||
b.setWaterArea(Float.valueOf(waterArea)); |
||||
} |
||||
if(lockTransparency != null) { |
||||
b.setLockTransparency("true".equals(lockTransparency)); |
||||
} |
||||
} |
||||
} |
||||
|
||||
synchronized private void saveProps() { |
||||
if(propsFile == null) { |
||||
return; |
||||
} |
||||
|
||||
props.setProperty("file.version", "1"); // Increase this when making format changes!
|
||||
|
||||
props.setProperty("sound.enabled", soundEnabled ? "true" : "false"); |
||||
|
||||
props.setProperty("zoom.smooth", userInterface.isSmoothZoom() ? "true" : "false"); |
||||
|
||||
props.setProperty("tags.enabled", userInterface.getEditor().isTagsEnabled() ? "true" : "false"); |
||||
props.setProperty("cursor.software", userInterface.getEditor().isSoftwareCursorEnabled() ? "true" : "false"); |
||||
|
||||
for(JCollapsiblePanel panel : userInterface.getToolPanels()) { |
||||
props.setProperty("panel" + panel.getId() + ".expanded", panel.isExpanded() ? "true" : "false"); |
||||
props.setProperty("panel" + panel.getId() + ".stuck", panel.isStuck() ? "true" : "false"); |
||||
} |
||||
|
||||
for(int i = 0; i < brushes.length; i++) { |
||||
props.setProperty("brush" + i + ".name", brushes[i].getName()); |
||||
props.setProperty("brush" + i + ".opacity", String.valueOf(brushes[i].getOpacity())); |
||||
props.setProperty("brush" + i + ".flow", String.valueOf(brushes[i].getFlow())); |
||||
props.setProperty("brush" + i + ".radius", String.valueOf(brushes[i].getRadius())); |
||||
props.setProperty("brush" + i + ".hardness", String.valueOf(brushes[i].getHardness())); |
||||
props.setProperty("brush" + i + ".spacing", String.valueOf(brushes[i].getSpacing())); |
||||
props.setProperty("brush" + i + ".pressureToOpacity", brushes[i].isPressureToOpacity() ? "true" : "false"); |
||||
props.setProperty("brush" + i + ".pressureToFlow", brushes[i].isPressureToFlow() ? "true" : "false"); |
||||
props.setProperty("brush" + i + ".pressureToRadius", brushes[i].isPressureToRadius() ? "true" : "false"); |
||||
props.setProperty("brush" + i + ".jitter", String.valueOf(brushes[i].getJitter())); |
||||
props.setProperty("brush" + i + ".noise", String.valueOf(brushes[i].getNoise())); |
||||
props.setProperty("brush" + i + ".water", String.valueOf(brushes[i].getWater())); |
||||
props.setProperty("brush" + i + ".waterArea", String.valueOf(brushes[i].getWaterArea())); |
||||
props.setProperty("brush" + i + ".lockTransparency", brushes[i].isLockTransparency() ? "true" : "false"); |
||||
} |
||||
|
||||
try { |
||||
FileOutputStream os = new FileOutputStream(propsFile); |
||||
props.store(os, "Sketcher Properties File"); |
||||
os.close(); |
||||
} catch(IOException e) { |
||||
|
||||
} catch(SecurityException e) { |
||||
propsTimer.cancel(); |
||||
} |
||||
} |
||||
|
||||
public void close(String message) { |
||||
propsTimer.cancel(); |
||||
saveProps(); |
||||
|
||||
/** Empty the send queue */ |
||||
connection.getSendQueue().clear(); |
||||
|
||||
/** Send sign-out command */ |
||||
connection.getSendQueue().offer(new CommandEntry(0, new SignOutCommand(message))); |
||||
|
||||
/** Schedule connection death */ |
||||
connection.setTimeOfDeath(System.currentTimeMillis() + 1000); |
||||
|
||||
/** Play sound */ |
||||
if("true".equals(props.getProperty("sound.enabled", "true"))) { |
||||
UserInterface.AUDIO_OUTTRO.play(); |
||||
} |
||||
} |
||||
|
||||
public TwoWayHashMap<Integer, User> getUserMap() { |
||||
return userMap; |
||||
} |
||||
|
||||
public User[] getUserArray() { |
||||
return userArray; |
||||
} |
||||
|
||||
public Canvas getCanvas() { |
||||
return canvas; |
||||
} |
||||
|
||||
public void setCanvas(Canvas canvas) { |
||||
this.canvas = canvas; |
||||
userInterface.setCanvas(canvas); |
||||
} |
||||
|
||||
public UserInterface getUserInterface() { |
||||
return userInterface; |
||||
} |
||||
|
||||
public Connection getConnection() { |
||||
return connection; |
||||
} |
||||
|
||||
public void run() { |
||||
try { |
||||
/** Create client connection */ |
||||
connection = new Connection(SocketChannel.open()); |
||||
|
||||
userInterface.println("Connecting... "); |
||||
|
||||
/** Enable blocking for connect */ |
||||
connection.getChannel().configureBlocking(true); |
||||
|
||||
/** Attempt to connect to server */ |
||||
connection.getChannel().connect(new InetSocketAddress(hostname, port)); |
||||
|
||||
/** Disable blocking after connect */ |
||||
connection.getChannel().configureBlocking(false); |
||||
|
||||
userInterface.print("Success!"); |
||||
|
||||
/** Play audio */ |
||||
if("true".equals(props.getProperty("sound.enabled", "true"))) { |
||||
UserInterface.AUDIO_INTRO.play(); |
||||
} |
||||
|
||||
/** Create command buffer */ |
||||
commandBuffer = ByteBuffer.allocate(65538); |
||||
|
||||
/** Send sign-in command */ |
||||
connection.getSendQueue().offer(new CommandEntry(0, new SignInCommand(0, login, password, false))); |
||||
|
||||
/** Set last ping time */ |
||||
connection.setLastPing(0); |
||||
|
||||
while(!Thread.interrupted()) { |
||||
if(!connection.getChannel().isConnected()) { |
||||
userInterface.println("Disconnected (Connection was closed)"); |
||||
break; |
||||
} else if(connection.isTimeOfDeath()) { |
||||
try { connection.getChannel().close(); } catch(IOException e) { } |
||||
userInterface.println("Disconnected (Exit)"); |
||||
break; |
||||
} |
||||
|
||||
if(System.currentTimeMillis() - connection.getLastPing() >= Connection.PING_INTERVAL) { |
||||
long timestamp = System.currentTimeMillis(); |
||||
connection.getSendQueue().offer(new CommandEntry(0, new PingCommand(timestamp))); |
||||
connection.setLastPing(timestamp); |
||||
} |
||||
|
||||
for(CommandEntry e = commandQueue.poll(20, TimeUnit.MILLISECONDS); e != null; e = commandQueue.poll(20, TimeUnit.MILLISECONDS)) { |
||||
if(e.getCommand().perform(this, e.getSourceKey() == 0 ? connection.getUser() : userMap.get(e.getSourceKey())) == Connection.SEND_OTHERS) { |
||||
connection.getSendQueue().offer(new CommandEntry(e.getSourceKey(), e.getCommand())); |
||||
} |
||||
} |
||||
|
||||
if(canvas != null && lastLayerClean + 30000 < System.currentTimeMillis()) { |
||||
lastLayerClean = System.currentTimeMillis(); |
||||
for(Layer e : canvas.getLayerMap().values()) { |
||||
e.clean(); |
||||
} |
||||
} |
||||
|
||||
ByteBuffer inputBuffer = connection.getInputBuffer(); |
||||
ByteBuffer outputBuffer = connection.getOutputBuffer(); |
||||
|
||||
ArrayBlockingQueue<CommandEntry> sendQueue = connection.getSendQueue(); |
||||
|
||||
//int startPosition = outputBuffer.position();
|
||||
|
||||
/** Only check queue if room for command length, peer key, name length, and one character of name */ |
||||
while(outputBuffer.remaining() > 2 + 4 + 1 + 2 && sendQueue.size() > 0) { |
||||
CommandEntry e = sendQueue.peek(); |
||||
Integer k = e.getSourceKey(); |
||||
Command c = e.getCommand(); |
||||
|
||||
/** Clear command buffer */ |
||||
commandBuffer.clear(); |
||||
|
||||
/** Write peer key */ |
||||
commandBuffer.putInt(k); |
||||
|
||||
/** Write command name */ |
||||
String commandName = c.getClass().getSimpleName(); |
||||
commandBuffer.put((byte) (commandName.length() - 1)); |
||||
for(int i = 0; i < commandName.length(); i++) { |
||||
commandBuffer.putChar(commandName.charAt(i)); |
||||
} |
||||
|
||||
/** Write command */ |
||||
c.encode(commandBuffer); |
||||
|
||||
/** Flip command buffer before reading */ |
||||
commandBuffer.flip(); |
||||
|
||||
/** Do not send this command yet if no room in output buffer */ |
||||
if(commandBuffer.remaining() + 2 > outputBuffer.remaining()) { |
||||
break; |
||||
} |
||||
|
||||
/** Write append command length and command data to output buffer */ |
||||
outputBuffer.putShort((short) (commandBuffer.remaining() - 1)); |
||||
outputBuffer.put(commandBuffer); |
||||
|
||||
/** Remove command from queue */ |
||||
sendQueue.remove(); |
||||
} |
||||
|
||||
// Primitive encryption
|
||||
//for(int i = startPosition; i < outputBuffer.position(); i++) {
|
||||
// outputBuffer.put(i, (byte) (outputBuffer.get(i) ^ sendRandom.nextInt(256)));
|
||||
//}
|
||||
|
||||
/** Flip output buffer */ |
||||
outputBuffer.flip(); |
||||
|
||||
if(outputBuffer.remaining() > 0) { |
||||
connection.getChannel().write(outputBuffer); |
||||
} |
||||
|
||||
/** Compact buffer, preparing it for appending */ |
||||
outputBuffer.compact(); |
||||
|
||||
if(canvas == null || !canvas.isDrawing()) { |
||||
try { |
||||
/** Append to input buffer */ |
||||
//startPosition = inputBuffer.position();
|
||||
int readResult = connection.getChannel().read(inputBuffer); |
||||
if(readResult == -1) { |
||||
try { connection.getChannel().close(); } catch(IOException e) { } |
||||
userInterface.println("Disconnected (Connection was closed)"); |
||||
break; |
||||
} else if(readResult > 0) { |
||||
//for(int i = startPosition; i < inputBuffer.position(); i++) {
|
||||
// inputBuffer.put(i, (byte) (inputBuffer.get(i) ^ recvRandom.nextInt(256)));
|
||||
//}
|
||||
|
||||
/** Prepare buffer for reading */ |
||||
inputBuffer.flip(); |
||||
|
||||
/** Long enough to peek at length? */ |
||||
while((canvas == null || !canvas.isDrawing()) && inputBuffer.remaining() >= 2) { |
||||
/** Peek at length */ |
||||
int commandLength = (inputBuffer.getShort(inputBuffer.position()) & 0xFFFF) + 1; |
||||
|
||||
/** Don't read if not long enough to read entire command. */ |
||||
if(inputBuffer.remaining() < commandLength + 2) { |
||||
break; |
||||
} |
||||
|
||||
/** We already have the command length */ |
||||
inputBuffer.getShort(); |
||||
|
||||
/** Read command buffer */ |
||||
ByteBuffer commandBuffer = inputBuffer.slice(); |
||||
commandBuffer.limit(commandLength); |
||||
inputBuffer.position(inputBuffer.position() + commandLength); |
||||
|
||||
/** Read user key */ |
||||
Integer userKey = commandBuffer.getInt(); |
||||
|
||||
/** Read command name */ |
||||
int commandNameLength = (commandBuffer.get() & 0xFF) + 1; |
||||
StringBuffer commandNameBuffer = new StringBuffer(); |
||||
for(int i = 0; i < commandNameLength; i++) { |
||||
commandNameBuffer.append(commandBuffer.getChar()); |
||||
} |
||||
String commandName = commandNameBuffer.toString(); |
||||
|
||||
Command command = (Command) StreamableUtils.create("com.jotuntech.sketcher.client.command." + commandName, commandBuffer); |
||||
command.perform(this, userMap.get(userKey)); |
||||
|
||||
//++iterations;
|
||||
} |
||||
|
||||
/** Prepare input buffer for appending */ |
||||
inputBuffer.compact(); |
||||
} |
||||
} catch (IOException e) { |
||||
try { connection.getChannel().close(); } catch(IOException e2) { } |
||||
userInterface.println("Disconnected (Connection was broken: " + e.getMessage() + ")"); |
||||
break; |
||||
} catch(Throwable t) { |
||||
sendQueue.clear(); |
||||
sendQueue.offer(new CommandEntry(0, new SignOutCommand("Client crashed: " + t.getClass().getSimpleName()))); |
||||
connection.setTimeOfDeath(System.currentTimeMillis() + 5000); |
||||
Log.error(t); |
||||
userInterface.println("Disconnected (Client crashed: " + t.getClass().getSimpleName() + ")"); |
||||
} |
||||
} |
||||
} |
||||
} catch (InterruptedException e) { |
||||
Log.debug("Client interrupted."); |
||||
} catch (IOException e) { |
||||
userInterface.println("Disconnected (Connection was broken: " + e.getMessage() + ")"); |
||||
Log.error(e); |
||||
} |
||||
|
||||
try { connection.getChannel().close(); } catch(IOException e) { } |
||||
connection.setChannel(null); |
||||
connection.setUser(null); |
||||
|
||||
if("true".equals(props.getProperty("sound.enabled", "true"))) { |
||||
UserInterface.AUDIO_OUTTRO.play(); |
||||
} |
||||
} |
||||
|
||||
public BlockingQueue<CommandEntry> getCommandQueue() { |
||||
return commandQueue; |
||||
} |
||||
|
||||
public boolean isVoiceEnabled() { |
||||
return voiceClient != null; |
||||
} |
||||
|
||||
public void setVoiceEnabled(boolean voiceEnabled) { |
||||
if(voiceEnabled) { |
||||
final Integer peerKey = getUserMap().getKeyForValue(getConnection().getUser()); |
||||
voiceClient = new VoiceClient(new InetSocketAddress(hostname, port + 100), peerKey); |
||||
voiceClient.setListener(new VoiceListener() { |
||||
public void voiceEvent(VoiceEvent e) { |
||||
switch(e.getType()) { |
||||
case VoiceEvent.TYPE_CHANNEL_NEW: |
||||
Integer userKey = e.getChannel(); |
||||
User user = userMap.get(userKey); |
||||
if(user == null) { |
||||
break; |
||||
} |
||||
break; |
||||
|
||||
case VoiceEvent.TYPE_CHANNEL_DEAD: { |
||||
JUserEntry ue = userInterface.getUserList().getEntry(e.getChannel()); |
||||
if(ue == null) { |
||||
break; |
||||
} |
||||
ue.clearVoice(); |
||||
break; |
||||
} |
||||
|
||||
case VoiceEvent.TYPE_PACKET_VOLUME: { |
||||
JUserEntry ue = userInterface.getUserList().getEntry(e.getChannel()); |
||||
if(ue == null) { |
||||
break; |
||||
} |
||||
if(e.getChannel() == peerKey) { |
||||
ue.setVolume(e.getVolume(), 0); |
||||
} else { |
||||
ue.setVolume(e.getVolume(), 12); |
||||
} |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
}); |
||||
voiceClient.start(); |
||||
} else { |
||||
voiceClient.interrupt(); |
||||
try { voiceClient.join(); } catch (InterruptedException e) { } |
||||
voiceClient = null; |
||||
userInterface.getUserList().clearVoice(); |
||||
} |
||||
} |
||||
public VoiceClient getVoiceClient() { |
||||
return voiceClient; |
||||
} |
||||
|
||||
public Properties getProps() { |
||||
return props; |
||||
} |
||||
|
||||
public void setSoundEnabled(boolean soundEnabled) { |
||||
this.soundEnabled = soundEnabled; |
||||
} |
||||
|
||||
public boolean isSoundEnabled() { |
||||
return soundEnabled; |
||||
} |
||||
|
||||
public void setAds(boolean ads) { |
||||
this.ads = ads; |
||||
} |
||||
|
||||
public boolean isAds() { |
||||
return ads; |
||||
} |
||||
|
||||
public String getLogin() { |
||||
return login; |
||||
} |
||||
|
||||
public String getPassword() { |
||||
return password; |
||||
} |
||||
|
||||
public String getHostname() { |
||||
return hostname; |
||||
} |
||||
} |
@ -0,0 +1,54 @@ |
||||
package com.jotuntech.sketcher.client; |
||||
|
||||
import java.awt.HeadlessException; |
||||
|
||||
import javax.swing.JApplet; |
||||
import javax.swing.JOptionPane; |
||||
|
||||
import com.jotuntech.sketcher.common.Log; |
||||
|
||||
public class ClientApplet extends JApplet { |
||||
private Client client; |
||||
String login, password, hostname; |
||||
int port; |
||||
boolean ads; |
||||
|
||||
public ClientApplet() throws HeadlessException { |
||||
super(); |
||||
} |
||||
|
||||
public void init() { |
||||
login = getParameter("nick"); |
||||
password = getParameter("secret"); |
||||
hostname = getParameter("server"); |
||||
port = Integer.valueOf(getParameter("port")); |
||||
ads = "1".equals(getParameter("ads")); |
||||
|
||||
Log.info("Sketcher applet is initializing."); |
||||
} |
||||
|
||||
public void start() { |
||||
System.gc(); |
||||
Log.info("Sketcher applet is starting."); |
||||
if(Runtime.getRuntime().totalMemory() < 92274688) { |
||||
JOptionPane.showMessageDialog(this, "There is too little Java heap memory space for Sketcher to function.\nYour platform likely requires manual configuration. Please pass the\nfollowing run-time parameters to your Java Virtual Machine:\n\n -ms96M -mx384M\n\nContact the website administrator for further assistance."); |
||||
} |
||||
client = new Client(hostname, port, login, password, ads); |
||||
getContentPane().removeAll(); |
||||
getContentPane().add(client.getUserInterface()); |
||||
client.start(); |
||||
} |
||||
|
||||
public void stop() { |
||||
Log.info("Sketcher applet is stopping."); |
||||
client.close("Exit"); |
||||
System.gc(); |
||||
} |
||||
|
||||
public void destroy() { |
||||
Log.info("Sketcher applet is being destroyed."); |
||||
getContentPane().removeAll(); |
||||
client = null; |
||||
} |
||||
} |
||||
|
@ -0,0 +1,8 @@ |
||||
package com.jotuntech.sketcher.client; |
||||
|
||||
import com.jotuntech.sketcher.common.Streamable; |
||||
import com.jotuntech.sketcher.common.User; |
||||
|
||||
public interface Command extends Streamable { |
||||
public int perform(Client client, User user); |
||||
} |
@ -0,0 +1,24 @@ |
||||
package com.jotuntech.sketcher.client; |
||||
|
||||
public class CommandEntry { |
||||
private Integer sourceKey; |
||||
private Command command; |
||||
|
||||
public CommandEntry(Integer sourceKey, Command command) { |
||||
if(sourceKey == null) { |
||||
throw new NullPointerException("Source key can't be null."); |
||||
} else if(command == null) { |
||||
throw new NullPointerException("Command can't be null."); |
||||
} |
||||
this.sourceKey = sourceKey; |
||||
this.command = command; |
||||
} |
||||
|
||||
public Integer getSourceKey() { |
||||
return sourceKey; |
||||
} |
||||
|
||||
public Command getCommand() { |
||||
return command; |
||||
} |
||||
} |
@ -0,0 +1,113 @@ |
||||
/** |
||||
* |
||||
*/ |
||||
package com.jotuntech.sketcher.client; |
||||
|
||||
import java.io.IOException; |
||||
import java.nio.ByteBuffer; |
||||
import java.nio.channels.SocketChannel; |
||||
import java.util.concurrent.ArrayBlockingQueue; |
||||
|
||||
import com.jotuntech.sketcher.common.User; |
||||
|
||||
/** |
||||
* Client connection |
||||
* |
||||
* @author Thor Harald Johansen |
||||
*/ |
||||
public class Connection { |
||||
/** Return values for Command */ |
||||
public final static int SEND_NONE = 0; /** Send to nobody */ |
||||
public final static int SEND_SELF = 1; /** Send to self only */ |
||||
public final static int SEND_OTHERS = 2; /** Send to others only */ |
||||
public final static int SEND_ALL = 3; /** Send to self and others */ |
||||
|
||||
public final static long PING_INTERVAL = 10000; |
||||
|
||||
/** Socket channel */ |
||||
private SocketChannel channel; |
||||
|
||||
/** User to which connection belongs. */ |
||||
private User user; |
||||
|
||||
/** Input buffer */ |
||||
private ByteBuffer inputBuffer; |
||||
|
||||
/** Output buffer */ |
||||
private ByteBuffer outputBuffer; |
||||
|
||||
/** Send queue */ |
||||
private ArrayBlockingQueue<CommandEntry> sendQueue = new ArrayBlockingQueue<CommandEntry>(98304); |
||||
|
||||
/** Time of death */ |
||||
private long timeOfDeath; |
||||
|
||||
/** Last ping */ |
||||
private long lastPing; |
||||
|
||||
public Connection() { |
||||
|
||||
} |
||||
|
||||
public Connection(SocketChannel channel) throws IOException { |
||||
/** Store socket channel. */ |
||||
this.channel = channel; |
||||
|
||||
/** Allocate transmission buffers */ |
||||
inputBuffer = ByteBuffer.allocate(65538); |
||||
outputBuffer = ByteBuffer.allocate(65538); |
||||
|
||||
/** Disable time of death */ |
||||
timeOfDeath = Long.MAX_VALUE; |
||||
} |
||||
|
||||
/** Set user for connection */ |
||||
public void setUser(User user) { |
||||
this.user = user; |
||||
} |
||||
|
||||
/** Get user for connection */ |
||||
public User getUser() { |
||||
return user; |
||||
} |
||||
|
||||
public void setChannel(SocketChannel channel) { |
||||
this.channel = channel; |
||||
} |
||||
|
||||
public SocketChannel getChannel() { |
||||
return channel; |
||||
} |
||||
|
||||
public ByteBuffer getInputBuffer() { |
||||
return inputBuffer; |
||||
} |
||||
|
||||
public ByteBuffer getOutputBuffer() { |
||||
return outputBuffer; |
||||
} |
||||
|
||||
public ArrayBlockingQueue<CommandEntry> getSendQueue() { |
||||
return sendQueue; |
||||
} |
||||
|
||||
public void setTimeOfDeath(long timeOfDeath) { |
||||
this.timeOfDeath = timeOfDeath; |
||||
} |
||||
|
||||
public boolean hasTimeOfDeath() { |
||||
return timeOfDeath != Long.MAX_VALUE; |
||||
} |
||||
|
||||
public boolean isTimeOfDeath() { |
||||
return timeOfDeath != Long.MAX_VALUE && System.currentTimeMillis() >= timeOfDeath; |
||||
} |
||||
|
||||
public void setLastPing(long lastPing) { |
||||
this.lastPing = lastPing; |
||||
} |
||||
|
||||
public long getLastPing() { |
||||
return lastPing; |
||||
} |
||||
} |
@ -0,0 +1,154 @@ |
||||
package com.jotuntech.sketcher.client; |
||||
|
||||
import java.awt.BorderLayout; |
||||
import java.awt.Color; |
||||
import java.awt.Component; |
||||
import java.awt.event.MouseEvent; |
||||
import java.awt.event.MouseListener; |
||||
|
||||
import javax.swing.Icon; |
||||
import javax.swing.ImageIcon; |
||||
import javax.swing.JLabel; |
||||
import javax.swing.JPanel; |
||||
import javax.swing.UIManager; |
||||
import javax.swing.border.EmptyBorder; |
||||
|
||||
public class JCollapsiblePanel extends JPanel { |
||||
private final static Icon expandedIcon = UIManager.getIcon("Tree.expandedIcon"); |
||||
private final static Icon collapsedIcon = UIManager.getIcon("Tree.collapsedIcon"); |
||||
private final static Icon pinNormalIcon = new ImageIcon(JCollapsiblePanel.class.getResource("images/pin_normal.png")); |
||||
private final static Icon pinStuckIcon = new ImageIcon(JCollapsiblePanel.class.getResource("images/pin_stuck.png")); |
||||
private JLabel label; |
||||
private JLabel pin; |
||||
private JPanel contentPane; |
||||
private JCollapsiblePanelGroup group; |
||||
private boolean stuck; |
||||
private int id; |
||||
private boolean expandedByDefault, stuckByDefault; |
||||
|
||||
public JCollapsiblePanel(int id, String title, boolean expanded, boolean stuckx) { |
||||
super(); |
||||
|
||||
this.id = id; |
||||
|
||||
this.stuck = stuckx; |
||||
this.stuckByDefault = stuckx; |
||||
this.expandedByDefault = expanded; |
||||
|
||||
setBorder(new EmptyBorder(0, 0, 1, 0)); |
||||
setAlignmentX(Component.LEFT_ALIGNMENT); |
||||
setLayout(new BorderLayout()); |
||||
|
||||
JPanel topPanel = new JPanel(new BorderLayout()); |
||||
topPanel.setAlignmentX(Component.LEFT_ALIGNMENT); |
||||
add(topPanel, BorderLayout.NORTH); |
||||
|
||||
label = new JLabel(); |
||||
label.setIcon(expanded ? expandedIcon : collapsedIcon); |
||||
label.setText(title); |
||||
label.addMouseListener(new MouseListener() { |
||||
public void mouseClicked(MouseEvent arg0) { |
||||
} |
||||
|
||||
public void mouseEntered(MouseEvent arg0) { |
||||
} |
||||
|
||||
public void mouseExited(MouseEvent arg0) { |
||||
} |
||||
|
||||
public void mousePressed(MouseEvent arg0) { |
||||
} |
||||
|
||||
public void mouseReleased(MouseEvent arg0) { |
||||
if(!stuck) { |
||||
setExpanded(!contentPane.isVisible(), true); |
||||
} |
||||
} |
||||
}); |
||||
label.setAlignmentX(Component.LEFT_ALIGNMENT); |
||||
label.setBackground(new Color(172, 200, 230)); |
||||
label.setOpaque(true); |
||||
topPanel.add(label, BorderLayout.CENTER); |
||||
|
||||
pin = new JLabel(); |
||||
pin.setIcon(stuck ? pinStuckIcon : pinNormalIcon); |
||||
pin.addMouseListener(new MouseListener() { |
||||
public void mouseClicked(MouseEvent e) { |
||||
} |
||||
|
||||
public void mouseEntered(MouseEvent e) { |
||||
} |
||||
|
||||
public void mouseExited(MouseEvent e) { |
||||
} |
||||
|
||||
public void mousePressed(MouseEvent e) { |
||||
} |
||||
|
||||
public void mouseReleased(MouseEvent e) { |
||||
setStuck(!isStuck()); |
||||
} |
||||
}); |
||||
|
||||
pin.setAlignmentX(Component.LEFT_ALIGNMENT); |
||||
pin.setBackground(new Color(172, 200, 230)); |
||||
pin.setOpaque(true); |
||||
topPanel.add(pin, BorderLayout.EAST); |
||||
|
||||
contentPane = new JPanel(); |
||||
contentPane.setVisible(expanded); |
||||
contentPane.setBorder(new EmptyBorder(4, 4, 4, 4)); |
||||
add(contentPane, BorderLayout.CENTER); |
||||
} |
||||
|
||||
public JPanel getContentPane() { |
||||
return contentPane; |
||||
} |
||||
|
||||
public void setExpanded(boolean expanded, boolean interactive) { |
||||
if(interactive && expanded && group != null) { |
||||
for(JCollapsiblePanel panel : group) { |
||||
if(panel == this || panel.isStuck()) { |
||||
continue; |
||||
} |
||||
panel.setExpanded(false, false); |
||||
} |
||||
} |
||||
contentPane.setVisible(expanded); |
||||
label.setIcon(expanded ? expandedIcon : collapsedIcon); |
||||
} |
||||
|
||||
public boolean isExpanded() { |
||||
return contentPane.isVisible(); |
||||
} |
||||
|
||||
public void setGroup(JCollapsiblePanelGroup group) { |
||||
this.group = group; |
||||
group.add(this); |
||||
} |
||||
|
||||
public void setStuck(boolean stuck) { |
||||
this.stuck = stuck; |
||||
pin.setIcon(stuck ? pinStuckIcon : pinNormalIcon); |
||||
} |
||||
|
||||
public boolean isStuck() { |
||||
return stuck; |
||||
} |
||||
|
||||
public void setId(int id) { |
||||
this.id = id; |
||||
} |
||||
|
||||
public int getId() { |
||||
return id; |
||||
} |
||||
|
||||
public boolean isExpandedByDefault() { |
||||
return expandedByDefault; |
||||
} |
||||
|
||||
public boolean isStuckByDefault() { |
||||
return stuckByDefault; |
||||
} |
||||
} |
@ -0,0 +1,7 @@ |
||||
package com.jotuntech.sketcher.client; |
||||
|
||||
import java.util.HashSet; |
||||
|
||||
public class JCollapsiblePanelGroup extends HashSet<JCollapsiblePanel> { |
||||
|
||||
} |
@ -0,0 +1,74 @@ |
||||
package com.jotuntech.sketcher.client; |
||||
|
||||
import java.awt.BorderLayout; |
||||
import java.awt.Dimension; |
||||
import java.util.Arrays; |
||||
|
||||
import javax.swing.BoxLayout; |
||||
import javax.swing.JLabel; |
||||
import javax.swing.JPanel; |
||||
import javax.swing.JProgressBar; |
||||
|
||||
public class JUserEntry extends JPanel { |
||||
private JLabel nameLabel; |
||||
private JPanel infoPanel; |
||||
private JProgressBar volumeBar; |
||||
private double slowVolume; |
||||
private int[] delayLine; |
||||
|
||||
public JUserEntry(String name) { |
||||
setLayout(new BorderLayout()); |
||||
|
||||
setOpaque(false); |
||||
|
||||
nameLabel = new JLabel(name); |
||||
add(nameLabel, BorderLayout.CENTER); |
||||
|
||||
infoPanel = new JPanel(); |
||||
infoPanel.setOpaque(false); |
||||
infoPanel.setLayout(new BoxLayout(infoPanel, BoxLayout.X_AXIS)); |
||||
add(infoPanel, BorderLayout.EAST); |
||||
|
||||
volumeBar = new JProgressBar(0, 32768); |
||||
volumeBar.setPreferredSize(new Dimension(64, 16)); |
||||
volumeBar.setOpaque(false); |
||||
volumeBar.setValue(32768); |
||||
volumeBar.setEnabled(false); |
||||
volumeBar.setToolTipText(name + " is not on voice."); |
||||
infoPanel.add(volumeBar); |
||||
} |
||||
|
||||
public void setVolume(int volume) { |
||||
setVolume(volume, 0); |
||||
} |
||||
|
||||
public void setVolume(int volume, int delay) { |
||||
if(volume <= 0) { |
||||
volume = 1; |
||||
} |
||||
if(delayLine == null || delayLine.length <= delay) { |
||||
delayLine = new int[delay + 1]; |
||||
Arrays.fill(delayLine, 1); |
||||
} |
||||
delayLine[delay] = volume; |
||||
double newVolume = Math.log(delayLine[0]) * 32768d / Math.log(32768); |
||||
|
||||
for(int i = 0; i < delayLine.length - 1; i++) { |
||||
delayLine[i] = delayLine[i + 1]; |
||||
} |
||||
delayLine[delayLine.length - 1] = 1; |
||||
|
||||
slowVolume += (newVolume - slowVolume) * 0.333d; |
||||
volumeBar.setValue((int) Math.round(slowVolume)); |
||||
if(!volumeBar.isEnabled()) { |
||||
volumeBar.setEnabled(true); |
||||
volumeBar.setToolTipText(nameLabel.getText() + " is on voice."); |
||||
} |
||||
} |
||||
|
||||
public void clearVoice() { |
||||
volumeBar.setValue(32768); |
||||
volumeBar.setEnabled(false); |
||||
volumeBar.setToolTipText(nameLabel.getText() + " is not on voice."); |
||||
} |
||||
} |
@ -0,0 +1,57 @@ |
||||
package com.jotuntech.sketcher.client; |
||||
|
||||
import java.awt.Color; |
||||
import java.util.HashMap; |
||||
import java.util.Map; |
||||
|
||||
import javax.swing.BoxLayout; |
||||
import javax.swing.JPanel; |
||||
import javax.swing.event.ChangeEvent; |
||||
import javax.swing.event.ChangeListener; |
||||
|
||||
import com.jotuntech.sketcher.common.TwoWayHashMap; |
||||
import com.jotuntech.sketcher.common.User; |
||||
|
||||
public class JUserList extends JPanel { |
||||
TwoWayHashMap<Integer, User> userMap; |
||||
Map<Integer, JUserEntry> entryMap; |
||||
|
||||
public JUserList(TwoWayHashMap<Integer, User> userMap) { |
||||
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); |
||||
|
||||
this.setBackground(Color.WHITE); |
||||
this.setOpaque(true); |
||||
|
||||
entryMap = new HashMap<Integer, JUserEntry>(); |
||||
|
||||
this.userMap = userMap; |
||||
userMap.addChangeListener(new ChangeListener() { |
||||
public void stateChanged(ChangeEvent e) { |
||||
redo(); |
||||
} |
||||
}); |
||||
|
||||
redo(); |
||||
} |
||||
|
||||
private void redo() { |
||||
entryMap.clear(); |
||||
removeAll(); |
||||
for(Map.Entry<Integer, User> e : userMap.entrySet()) { |
||||
JUserEntry ue = new JUserEntry(e.getValue().getName()); |
||||
add(ue); |
||||
entryMap.put(e.getKey(), ue); |
||||
} |
||||
this.revalidate(); |
||||
} |
||||
|
||||
public JUserEntry getEntry(Integer key) { |
||||
return entryMap.get(key); |
||||
} |
||||
|
||||
public void clearVoice() { |
||||
for(JUserEntry ue : entryMap.values()) { |
||||
ue.clearVoice(); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,81 @@ |
||||
package com.jotuntech.sketcher.client; |
||||
|
||||
import javax.swing.UIManager; |
||||
import javax.swing.UIManager.LookAndFeelInfo; |
||||
|
||||
/** |
||||
* An collection of built-in Swing look and feels. This saves clients the |
||||
* trouble of manually setting the look and feel. |
||||
* |
||||
* @author Aaron Faanes |
||||
* |
||||
* @see javax.swing.LookAndFeel |
||||
*/ |
||||
public enum LookAndFeel { |
||||
|
||||
/** |
||||
* Represents the Nimbus look-and-feel that was added in Java 6 Update |
||||
* 10. |
||||
*/ |
||||
NIMBUS() { |
||||
|
||||
private volatile String name; |
||||
|
||||
@Override |
||||
public String getName() { |
||||
if (this.name == null) { |
||||
for (final LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) { |
||||
if (info.getClassName().contains("nimbus")) { |
||||
this.name = info.getClassName(); |
||||
} |
||||
} |
||||
} |
||||
return this.name; |
||||
} |
||||
|
||||
}, |
||||
|
||||
/** |
||||
* Represents the look-and-feel that emulates the system |
||||
* |
||||
* @see UIManager#getSystemLookAndFeelClassName() |
||||
*/ |
||||
SYSTEM() { |
||||
|
||||
@Override |
||||
public String getName() { |
||||
return UIManager.getSystemLookAndFeelClassName(); |
||||
} |
||||
}; |
||||
|
||||
/** |
||||
* Returns the name of the class that implements this look-and-feel. The |
||||
* returned name, if non-null, can be directly used in a |
||||
* {@link UIManager#setLookAndFeel(String)} invocation. If the returned |
||||
* value is null, then no class name could be found. |
||||
* |
||||
* |
||||
* @return the name of the class that implements this look-and-feel, or |
||||
* {@code null} if no class name could be found |
||||
*/ |
||||
public abstract String getName(); |
||||
|
||||
/** |
||||
* Sets the global look and feel to this look and feel. |
||||
* |
||||
* @return {@code true} if setting the look-and-feel was successful, |
||||
* otherwise {@code false} |
||||
*/ |
||||
public boolean activate() { |
||||
final String name = this.getName(); |
||||
if (name == null) { |
||||
return false; |
||||
} |
||||
try { |
||||
UIManager.setLookAndFeel(name); |
||||
return true; |
||||
} catch (final Exception e) { |
||||
return false; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,568 @@ |
||||
package com.jotuntech.sketcher.client; |
||||
|
||||
import java.awt.Point; |
||||
import java.awt.image.BufferedImage; |
||||
import java.awt.image.DataBufferInt; |
||||
import java.io.DataOutput; |
||||
import java.io.IOException; |
||||
import java.io.RandomAccessFile; |
||||
import java.util.Arrays; |
||||
import java.util.Map; |
||||
|
||||
import com.jotuntech.sketcher.common.BitmapLayer; |
||||
import com.jotuntech.sketcher.common.BitmapTile; |
||||
import com.jotuntech.sketcher.common.Layer; |
||||
import com.jotuntech.sketcher.common.TwoWayHashMap; |
||||
|
||||
public class PSDEncoder { |
||||
private RandomAccessFile raf; |
||||
private BufferedImage image; |
||||
private BitmapLayer[] layers; |
||||
private long[][] channelLengthPositions; |
||||
|
||||
public PSDEncoder(RandomAccessFile raf, BufferedImage image, TwoWayHashMap<Integer, Layer> layers) throws IOException { |
||||
this.raf = raf; |
||||
this.image = image; |
||||
|
||||
int layersLength = 0; |
||||
for(Layer l : layers.values()) { |
||||
if(l instanceof BitmapLayer) { |
||||
++layersLength; |
||||
} |
||||
} |
||||
|
||||
this.layers = new BitmapLayer[layersLength]; |
||||
int layerIndex = 0; |
||||
for(Layer l : layers.values()) { |
||||
if(l instanceof BitmapLayer) { |
||||
this.layers[layerIndex++] = (BitmapLayer) l; |
||||
} |
||||
} |
||||
|
||||
channelLengthPositions = new long[this.layers.length][4]; |
||||
} |
||||
|
||||
|
||||
public void encode() throws IOException { |
||||
/* Signature */ |
||||
raf.write(new byte[] {'8', 'B', 'P', 'S'}); |
||||
|
||||
/* Version */ |
||||
raf.writeShort(1); |
||||
|
||||
/* Reserved */ |
||||
raf.write(new byte[] {0, 0, 0, 0, 0, 0}); |
||||
|
||||
/* Number of channels */ |
||||
raf.writeShort(4); |
||||
|
||||
/* Image height */ |
||||
raf.writeInt(image.getHeight()); |
||||
|
||||
/* Image width */ |
||||
raf.writeInt(image.getWidth()); |
||||
|
||||
/* Bits per channel */ |
||||
raf.writeShort(8); |
||||
|
||||
/* Color mode: RGB color */ |
||||
raf.writeShort(3); |
||||
|
||||
/* Length of color mode data section */ |
||||
raf.writeInt(0); |
||||
|
||||
/* Length of image resources section */ |
||||
raf.writeInt(0); |
||||
|
||||
/* Layer and mask information */ |
||||
writeLayerAndMaskInfo(raf); |
||||
|
||||
/* Image data */ |
||||
writeImagePixelData(raf); |
||||
} |
||||
|
||||
private void writeLayerAndMaskInfo(RandomAccessFile raf) throws IOException { |
||||
long lengthPointer = raf.getFilePointer(); |
||||
raf.writeInt(0); |
||||
|
||||
/* Layer info */ |
||||
writeLayerInfo(raf); |
||||
|
||||
/* Mask info */ |
||||
writeMaskInfo(raf); |
||||
|
||||
/* Go back and write length */ |
||||
long endPointer = raf.getFilePointer(); |
||||
raf.seek(lengthPointer); |
||||
raf.writeInt((int)(endPointer - lengthPointer - 4)); |
||||
raf.seek(endPointer); |
||||
} |
||||
|
||||
|
||||
private void writeLayerInfo(RandomAccessFile odo) throws IOException { |
||||
System.err.println("Writing layer info."); |
||||
|
||||
long lengthPointer = raf.getFilePointer(); |
||||
odo.writeInt(0); |
||||
|
||||
/* Layers struct */ |
||||
writeLayersStruct(odo); |
||||
|
||||
/* Pixel data */ |
||||
writeAllLayersPixelData(odo); |
||||
|
||||
/* Go back and write length */ |
||||
long endPointer = raf.getFilePointer(); |
||||
raf.seek(lengthPointer); |
||||
raf.writeInt((int)(endPointer - lengthPointer - 4)); |
||||
raf.seek(endPointer); |
||||
} |
||||
|
||||
private void writeLayersStruct(RandomAccessFile odo) throws IOException { |
||||
System.err.println("Writing layers struct."); |
||||
|
||||
/* Layer count */ |
||||
odo.writeShort(layers.length); |
||||
|
||||
/* Layers */ |
||||
for(int layerIndex = 0; layerIndex < layers.length; layerIndex++) { |
||||
writeLayer(odo, layerIndex); |
||||
} |
||||
} |
||||
|
||||
private void writeLayer(RandomAccessFile odo, int layerIndex) throws IOException { |
||||
/* Get layer */ |
||||
BitmapLayer l = layers[layerIndex]; |
||||
|
||||
System.err.println("Writing layer: " + l.getName()); |
||||
|
||||
/* Layer top */ |
||||
odo.writeInt(0); |
||||
|
||||
/* Layer left */ |
||||
odo.writeInt(0); |
||||
|
||||
/* Layer bottom */ |
||||
odo.writeInt(image.getHeight()); |
||||
|
||||
/* Layer right */ |
||||
odo.writeInt(image.getWidth()); |
||||
|
||||
/* Number of channels */ |
||||
odo.writeShort(4); |
||||
|
||||
/* Channel length info */ |
||||
writeChannelLengthInfo(odo, layerIndex); |
||||
|
||||
/* Blend mode signature */ |
||||
odo.write(new byte[] {'8', 'B', 'I', 'M'}); |
||||
|
||||
/* Blend mode key */ |
||||
odo.write(new byte[] {'n', 'o', 'r', 'm'}); |
||||
|
||||
/* Opacity */ |
||||
odo.writeByte(Math.round(l.getOpacity() * 255f)); |
||||
|
||||
/* Clipping: Base */ |
||||
odo.writeByte(0); |
||||
|
||||
/* Flags: Transparency not protected and visible */ |
||||
odo.writeByte(0x80); |
||||
|
||||
/* Filler */ |
||||
odo.writeByte(0); |
||||
|
||||
/* Prepare layer name */ |
||||
String name = l.getName(); |
||||
int origLength = name.length(); |
||||
int moduloLength = (origLength + 1) % 4; |
||||
int padLength = 0; |
||||
if(moduloLength > 0) { |
||||
padLength = 4 - moduloLength; |
||||
for(int i = 0; i < padLength; i++) { |
||||
name += "\000"; |
||||
} |
||||
} |
||||
|
||||
/* Extra data size */ |
||||
odo.writeInt(4 + 44 + 1 + origLength + padLength); |
||||
|
||||
/* Layer mask data size */ |
||||
odo.writeInt(0); |
||||
|
||||
/* Layer blending ranges */ |
||||
writeLayerBlendingRanges(odo); |
||||
|
||||
odo.writeByte(origLength); |
||||
odo.writeBytes(name); |
||||
} |
||||
|
||||
private void writeChannelLengthInfo(RandomAccessFile raf, int layerIndex) throws IOException { |
||||
System.err.println("Writing channel length info."); |
||||
|
||||
/* Channel ID: Transparency Mask */ |
||||
raf.writeShort(-1); |
||||
|
||||
/* Channel length */ |
||||
channelLengthPositions[layerIndex][0] = raf.getFilePointer(); |
||||
raf.writeInt(0); |
||||
|
||||
/* Channel ID: Red */ |
||||
raf.writeShort(0); |
||||
|
||||
/* Channel length */ |
||||
channelLengthPositions[layerIndex][1] = raf.getFilePointer(); |
||||
raf.writeInt(0); |
||||
|
||||
/* Channel ID: Green */ |
||||
raf.writeShort(1); |
||||
|
||||
/* Channel length */ |
||||
channelLengthPositions[layerIndex][2] = raf.getFilePointer(); |
||||
raf.writeInt(0); |
||||
|
||||
/* Channel ID: Blue */ |
||||
raf.writeShort(2); |
||||
|
||||
/* Channel length */ |
||||
channelLengthPositions[layerIndex][3] = raf.getFilePointer(); |
||||
raf.writeInt(0); |
||||
} |
||||
|
||||
private void writeLayerBlendingRanges(DataOutput odo) throws IOException { |
||||
System.err.println("Writing layer blending ranges."); |
||||
|
||||
/* Length */ |
||||
odo.writeInt(40); |
||||
|
||||
/* Composite gray blend source */ |
||||
odo.writeInt(0x0000FFFF); |
||||
|
||||
/* Composite gray blend destination */ |
||||
odo.writeInt(0x0000FFFF); |
||||
|
||||
/* First channel source range */ |
||||
odo.writeInt(0x0000FFFF); |
||||
|
||||
/* First channel destination range */ |
||||
odo.writeInt(0x0000FFFF); |
||||
|
||||
/* Second channel source range */ |
||||
odo.writeInt(0x0000FFFF); |
||||
|
||||
/* Second channel destination range */ |
||||
odo.writeInt(0x0000FFFF); |
||||
|
||||
/* Third channel source range */ |
||||
odo.writeInt(0x0000FFFF); |
||||
|
||||
/* Third channel destination range */ |
||||
odo.writeInt(0x0000FFFF); |
||||
|
||||
/* Fourth channel source range */ |
||||
odo.writeInt(0x0000FFFF); |
||||
|
||||
/* Fourth channel destination range */ |
||||
odo.writeInt(0x0000FFFF); |
||||
} |
||||
|
||||
private void writeAllLayersPixelData(RandomAccessFile odo) throws IOException { |
||||
System.out.println("Writing pixel layer data."); |
||||
for(int layerIndex = 0; layerIndex < layers.length; layerIndex++) { |
||||
writeLayerPixelData(odo, layerIndex); |
||||
} |
||||
} |
||||
|
||||
private void writeMaskInfo(RandomAccessFile raf) throws IOException { |
||||
System.err.println("Writing mask info."); |
||||
|
||||
/* Mask info length */ |
||||
long lengthPointer = raf.getFilePointer(); |
||||
raf.writeInt(0); |
||||
|
||||
/* Overlay color space */ |
||||
raf.writeShort(0); |
||||
|
||||
/* Color components */ |
||||
raf.writeShort(0); |
||||
raf.writeShort(0); |
||||
raf.writeShort(0); |
||||
raf.writeShort(0); |
||||
|
||||
/* Opacity */ |
||||
raf.writeShort(0); |
||||
|
||||
/* Kind: Use value stored per layer */ |
||||
raf.writeByte(128); |
||||
|
||||
/* Filler */ |
||||
raf.writeByte(0); |
||||
|
||||
/* Go back and write length */ |
||||
long endPointer = raf.getFilePointer(); |
||||
raf.seek(lengthPointer); |
||||
raf.writeInt((int)(endPointer - lengthPointer - 4)); |
||||
raf.seek(endPointer); |
||||
} |
||||
|
||||
public void writeImagePixelData(RandomAccessFile odo) throws IOException { |
||||
System.out.println("Writing image data."); |
||||
|
||||
int[] pixels = ((DataBufferInt)image.getRaster().getDataBuffer()).getData(); |
||||
byte[] scanline = new byte[image.getWidth()]; |
||||
|
||||
/* Compression: RLE */ |
||||
odo.writeShort(1); |
||||
|
||||
/* Placeholder scanline bytecounts */ |
||||
long redCountsPosition = raf.getFilePointer(); |
||||
for(int i = 0; i < image.getHeight(); i++) { |
||||
odo.writeShort(0); |
||||
} |
||||
long greenCountsPosition = raf.getFilePointer(); |
||||
for(int i = 0; i < image.getHeight(); i++) { |
||||
odo.writeShort(0); |
||||
} |
||||
long blueCountsPosition = raf.getFilePointer(); |
||||
for(int i = 0; i < image.getHeight(); i++) { |
||||
odo.writeShort(0); |
||||
} |
||||
long alphaCountsPosition = raf.getFilePointer(); |
||||
for(int i = 0; i < image.getHeight(); i++) { |
||||
odo.writeShort(0); |
||||
} |
||||
|
||||
PackBitsOutputStream pbos = new PackBitsOutputStream(raf); |
||||
|
||||
/* Image data - Red */ |
||||
for(int y = 0; y < image.getHeight(); y++) { |
||||
for(int x = 0; x < scanline.length; x++) { |
||||
scanline[x] = (byte) ((pixels[y * scanline.length + x] >> 16) & 0xFF); |
||||
} |
||||
long scanlineStart = raf.getFilePointer(); |
||||
pbos.write(scanline); |
||||
pbos.flush(); |
||||
long scanlineStop = raf.getFilePointer(); |
||||
raf.seek(redCountsPosition + y * 2); |
||||
raf.writeShort((short)(scanlineStop - scanlineStart)); |
||||
raf.seek(scanlineStop); |
||||
} |
||||
|
||||
/* Image data - Green */ |
||||
for(int y = 0; y < image.getHeight(); y++) { |
||||
for(int x = 0; x < scanline.length; x++) { |
||||
scanline[x] = (byte) ((pixels[y * scanline.length + x] >> 8) & 0xFF); |
||||
} |
||||
long scanlineStart = raf.getFilePointer(); |
||||
pbos.write(scanline); |
||||
pbos.flush(); |
||||
long scanlineStop = raf.getFilePointer(); |
||||
raf.seek(greenCountsPosition + y * 2); |
||||
raf.writeShort((short)(scanlineStop - scanlineStart)); |
||||
raf.seek(scanlineStop); |
||||
} |
||||
|
||||
/* Image data - Blue */ |
||||
for(int y = 0; y < image.getHeight(); y++) { |
||||
for(int x = 0; x < scanline.length; x++) { |
||||
scanline[x] = (byte) (pixels[y * scanline.length + x] & 0xFF); |
||||
} |
||||
long scanlineStart = raf.getFilePointer(); |
||||
pbos.write(scanline); |
||||
pbos.flush(); |
||||
long scanlineStop = raf.getFilePointer(); |
||||
raf.seek(blueCountsPosition + y * 2); |
||||
raf.writeShort((short)(scanlineStop - scanlineStart)); |
||||
raf.seek(scanlineStop); |
||||
} |
||||
|
||||
/* Image data - Alpha */ |
||||
for(int y = 0; y < image.getHeight(); y++) { |
||||
for(int x = 0; x < scanline.length; x++) { |
||||
scanline[x] = (byte) ((pixels[y * scanline.length + x] >> 24) & 0xFF); |
||||
} |
||||
long scanlineStart = raf.getFilePointer(); |
||||
pbos.write(scanline); |
||||
pbos.flush(); |
||||
long scanlineStop = raf.getFilePointer(); |
||||
raf.seek(alphaCountsPosition + y * 2); |
||||
raf.writeShort((short)(scanlineStop - scanlineStart)); |
||||
raf.seek(scanlineStop); |
||||
} |
||||
} |
||||
|
||||
public void writeLayerPixelData(RandomAccessFile raf, int layerIndex) throws IOException { |
||||
System.out.println("Writing image data."); |
||||
|
||||
byte[] scanline = new byte[image.getWidth()]; |
||||
|
||||
/* Get tiles */ |
||||
Map<Point, BitmapTile> tiles = layers[layerIndex].getTiles(); |
||||
|
||||
PackBitsOutputStream pbos = new PackBitsOutputStream(raf); |
||||
|
||||
long startOfAlpha = raf.getFilePointer(); |
||||
int alphaLength; |
||||
|
||||
/* Compression: RLE */ |
||||
raf.writeShort(1); |
||||
|
||||
/* Alpha scanline counts */ |
||||
long alphaCountsPosition = raf.getFilePointer(); |
||||
for(int y = 0; y < image.getHeight(); y++) { |
||||
raf.writeShort(0); |
||||
} |
||||
|
||||
/* Alpha image data */ |
||||
for(int y = 0; y < image.getHeight(); y++) { |
||||
int yModulus = y & BitmapTile.SIZE_MASK; |
||||
for(int x = 0; x < scanline.length; x += BitmapTile.SIZE) { |
||||
BitmapTile t = tiles.get(new Point(x >> BitmapTile.SIZE_2, y >> BitmapTile.SIZE_2)); |
||||
if(t == null) { |
||||
Arrays.fill(scanline, x, x + BitmapTile.SIZE, (byte) 0); |
||||
} else { |
||||
int[] tilePixels = t.getPixels(); |
||||
for(int offset = 0; offset < BitmapTile.SIZE; offset++) { |
||||
scanline[x + offset] = (byte) ((tilePixels[(yModulus << BitmapTile.SIZE_2) + offset] >> 24) & 0xFF); |
||||
} |
||||
} |
||||
} |
||||
long scanlineStart = raf.getFilePointer(); |
||||
pbos.write(scanline); |
||||
pbos.flush(); |
||||
long scanlineEnd = raf.getFilePointer(); |
||||
raf.seek(alphaCountsPosition + y * 2); |
||||
raf.writeShort((short) (scanlineEnd - scanlineStart)); |
||||
raf.seek(scanlineEnd); |
||||
} |
||||
|
||||
long endOfAlpha = raf.getFilePointer(); |
||||
alphaLength = (int) (endOfAlpha - startOfAlpha); |
||||
raf.seek(channelLengthPositions[layerIndex][0]); |
||||
raf.writeInt(alphaLength); |
||||
raf.seek(endOfAlpha); |
||||
|
||||
long startOfRed = raf.getFilePointer(); |
||||
int redLength; |
||||
|
||||
/* Compression: RLE */ |
||||
raf.writeShort(1); |
||||
|
||||
/* Red scanline counts */ |
||||
long redCountsPosition = raf.getFilePointer(); |
||||
for(int y = 0; y < image.getHeight(); y++) { |
||||
raf.writeShort(0); |
||||
} |
||||
|
||||
/* Red image data */ |
||||
for(int y = 0; y < image.getHeight(); y++) { |
||||
int yModulus = y & BitmapTile.SIZE_MASK; |
||||
for(int x = 0; x < scanline.length; x += BitmapTile.SIZE) { |
||||
BitmapTile t = tiles.get(new Point(x >> BitmapTile.SIZE_2, y >> BitmapTile.SIZE_2)); |
||||
if(t == null) { |
||||
Arrays.fill(scanline, x, x + BitmapTile.SIZE, (byte) 0); |
||||
} else { |
||||
for(int offset = 0; offset < BitmapTile.SIZE; offset++) { |
||||
int[] tilePixels = t.getPixels(); |
||||
scanline[x + offset] = (byte) ((tilePixels[(yModulus << BitmapTile.SIZE_2) + offset] >> 16) & 0xFF); |
||||
} |
||||
} |
||||
} |
||||
long scanlineStart = raf.getFilePointer(); |
||||
pbos.write(scanline); |
||||
pbos.flush(); |
||||
long scanlineEnd = raf.getFilePointer(); |
||||
raf.seek(redCountsPosition + y * 2); |
||||
raf.writeShort((short) (scanlineEnd - scanlineStart)); |
||||
raf.seek(scanlineEnd); |
||||
} |
||||
|
||||
long endOfRed = raf.getFilePointer(); |
||||
redLength = (int) (endOfRed - startOfRed); |
||||
raf.seek(channelLengthPositions[layerIndex][1]); |
||||
raf.writeInt(redLength); |
||||
raf.seek(endOfRed); |
||||
|
||||
long startOfGreen = raf.getFilePointer(); |
||||
int greenLength; |
||||
|
||||
/* Compression: RLE */ |
||||
raf.writeShort(1); |
||||
|
||||
/* Green scanline counts */ |
||||
long greenCountsPosition = raf.getFilePointer(); |
||||
for(int y = 0; y < image.getHeight(); y++) { |
||||
raf.writeShort(0); |
||||
} |
||||
|
||||
/* Green image data */ |
||||
for(int y = 0; y < image.getHeight(); y++) { |
||||
int yModulus = y & BitmapTile.SIZE_MASK; |
||||
for(int x = 0; x < scanline.length; x += BitmapTile.SIZE) { |
||||
BitmapTile t = tiles.get(new Point(x >> BitmapTile.SIZE_2, y >> BitmapTile.SIZE_2)); |
||||
if(t == null) { |
||||
Arrays.fill(scanline, x, x + BitmapTile.SIZE, (byte) 0); |
||||
} else { |
||||
for(int offset = 0; offset < BitmapTile.SIZE; offset++) { |
||||
int[] tilePixels = t.getPixels(); |
||||
scanline[x + offset] = (byte) ((tilePixels[(yModulus << BitmapTile.SIZE_2) + offset] >> 8) & 0xFF); |
||||
} |
||||
} |
||||
} |
||||
long scanlineStart = raf.getFilePointer(); |
||||
pbos.write(scanline); |
||||
pbos.flush(); |
||||
long scanlineEnd = raf.getFilePointer(); |
||||
raf.seek(greenCountsPosition + y * 2); |
||||
raf.writeShort((short)(scanlineEnd - scanlineStart)); |
||||
raf.seek(scanlineEnd); |
||||
} |
||||
|
||||
long endOfGreen = raf.getFilePointer(); |
||||
greenLength = (int) (endOfGreen - startOfGreen); |
||||
raf.seek(channelLengthPositions[layerIndex][2]); |
||||
raf.writeInt(greenLength); |
||||
raf.seek(endOfGreen); |
||||
|
||||
long startOfBlue = raf.getFilePointer(); |
||||
int blueLength; |
||||
|
||||
/* Compression: RLE */ |
||||
raf.writeShort(1); |
||||
|
||||
/* Blue scanline counts */ |
||||
long blueCountsPosition = raf.getFilePointer(); |
||||
for(int y = 0; y < image.getHeight(); y++) { |
||||
raf.writeShort(0); |
||||
} |
||||
|
||||
/* Blue image data */ |
||||
for(int y = 0; y < image.getHeight(); y++) { |
||||
int yModulus = y & BitmapTile.SIZE_MASK; |
||||
for(int x = 0; x < scanline.length; x += BitmapTile.SIZE) { |
||||
BitmapTile t = tiles.get(new Point(x >> BitmapTile.SIZE_2, y >> BitmapTile.SIZE_2)); |
||||
if(t == null) { |
||||
Arrays.fill(scanline, x, x + BitmapTile.SIZE, (byte) 0); |
||||
} else { |
||||
int[] tilePixels = t.getPixels(); |
||||
for(int offset = 0; offset < BitmapTile.SIZE; offset++) { |
||||
scanline[x + offset] = (byte) (tilePixels[(yModulus << BitmapTile.SIZE_2) + offset] & 0xFF); |
||||
} |
||||
} |
||||
} |
||||
long scanlineStart = raf.getFilePointer(); |
||||
pbos.write(scanline); |
||||
pbos.flush(); |
||||
long scanlineEnd = raf.getFilePointer(); |
||||
raf.seek(blueCountsPosition + y * 2); |
||||
raf.writeShort((short)(scanlineEnd - scanlineStart)); |
||||
raf.seek(scanlineEnd); |
||||
} |
||||
|
||||
long endOfBlue = raf.getFilePointer(); |
||||
blueLength = (int) (endOfBlue - startOfBlue); |
||||
raf.seek(channelLengthPositions[layerIndex][3]); |
||||
raf.writeInt(blueLength); |
||||
raf.seek(endOfBlue); |
||||
} |
||||
} |
@ -0,0 +1,141 @@ |
||||
package com.jotuntech.sketcher.client; |
||||
|
||||
import java.io.DataOutput; |
||||
import java.io.IOException; |
||||
import java.io.OutputStream; |
||||
|
||||
public class PackBitsOutputStream extends OutputStream { |
||||
private DataOutput mdo; |
||||
|
||||
private final static int IN_BUFFER_SIZE = 2048; |
||||
private final static int IN_BUFFER_MASK = IN_BUFFER_SIZE - 1; |
||||
private final static int OUT_BUFFER_SIZE = 4096; |
||||
|
||||
private int[] inBuffer; |
||||
private int inBufferWritePos, inBufferReadPos, inBufferMark; |
||||
private byte[] outBuffer; |
||||
private int outBufferWritePos; |
||||
|
||||
public PackBitsOutputStream(DataOutput mdo) { |
||||
this.mdo = mdo; |
||||
this.inBuffer = new int[IN_BUFFER_SIZE]; |
||||
this.inBufferWritePos = 0; |
||||
this.inBufferReadPos = 0; |
||||
this.inBufferMark = 0; |
||||
this.outBuffer = new byte[OUT_BUFFER_SIZE]; |
||||
this.outBufferWritePos = 0; |
||||
} |
||||
|
||||
public void write(int b) throws IOException { |
||||
inBuffer[inBufferWritePos++ & IN_BUFFER_MASK] = b; |
||||
|
||||
if(inBufferWritePos - inBufferReadPos == IN_BUFFER_SIZE) { |
||||
pack(); |
||||
} |
||||
} |
||||
|
||||
public void write(byte[] b) throws IOException { |
||||
for(int i = 0; i < b.length; i++) { |
||||
inBuffer[inBufferWritePos++ & IN_BUFFER_MASK] = b[i]; |
||||
|
||||
if(inBufferWritePos - inBufferReadPos == IN_BUFFER_SIZE) { |
||||
pack(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
public void write(byte[] b, int off, int len) throws IOException { |
||||
for(int i = off; i < off + len; i++) { |
||||
inBuffer[inBufferWritePos++ & IN_BUFFER_MASK] = b[i]; |
||||
|
||||
if(inBufferWritePos - inBufferReadPos == IN_BUFFER_SIZE) { |
||||
pack(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
public void flush() throws IOException { |
||||
pack(); |
||||
} |
||||
|
||||
private void output(int b) { |
||||
outBuffer[outBufferWritePos++] = (byte) b; |
||||
} |
||||
|
||||
private void pack() throws IOException { |
||||
outBufferWritePos = 0; |
||||
|
||||
while(available() > 0) { |
||||
int b = read(); |
||||
int repeat = 1; |
||||
while(available() > 0 && peek() == b && repeat < 128) { |
||||
++repeat; |
||||
skip(); |
||||
} |
||||
|
||||
if(repeat == 1) { |
||||
mark(); |
||||
int b2 = read(); |
||||
int literal = 0; |
||||
while(available() > 0 && peek() != b2 && literal < 127) { |
||||
b2 = read(); |
||||
++literal; |
||||
} |
||||
|
||||
if(available() == 0 && literal < 127) { |
||||
++literal; |
||||
} |
||||
|
||||
if(literal == 0) { |
||||
reset(); |
||||
output(0); |
||||
output(b); |
||||
} else { |
||||
reset(); |
||||
output(literal); |
||||
output(b); |
||||
for(int i = 0; i < literal; i++) { |
||||
output(read()); |
||||
} |
||||
} |
||||
} else if(repeat == 2) { |
||||
output(1); |
||||
output(b); |
||||
output(b); |
||||
} else { |
||||
output(1 - repeat); |
||||
output(b); |
||||
} |
||||
} |
||||
|
||||
mdo.write(outBuffer, 0, outBufferWritePos); |
||||
} |
||||
|
||||
public void close() throws IOException { |
||||
pack(); |
||||
} |
||||
|
||||
private int available() { |
||||
return inBufferWritePos - inBufferReadPos; |
||||
} |
||||
|
||||
private int peek() { |
||||
return inBuffer[inBufferReadPos & IN_BUFFER_MASK]; |
||||
} |
||||
|
||||
private int read() { |
||||
return inBuffer[inBufferReadPos++ & IN_BUFFER_MASK]; |
||||
} |
||||
|
||||
private void skip() { |
||||
++inBufferReadPos; |
||||
} |
||||
|
||||
private void mark() { |
||||
inBufferMark = inBufferReadPos; |
||||
} |
||||
|
||||
private void reset() { |
||||
inBufferReadPos = inBufferMark; |
||||
} |
||||
} |
@ -0,0 +1,150 @@ |
||||
package com.jotuntech.sketcher.client; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
import com.jotuntech.sketcher.common.Input; |
||||
|
||||
/** |
||||
* Static singleton for input interpolation. |
||||
* |
||||
* @author Thor Harald Johansen |
||||
*/ |
||||
public class Smoother { |
||||
/* Smoothing threshold */ |
||||
private static float threshold = 4f; |
||||
private static float smoothness = 1.5f; |
||||
private static boolean enabled = true; |
||||
|
||||
Input[] cursor = new Input[] {new Input(), new Input(), new Input()}; |
||||
|
||||
/** |
||||
* Sets all points to given input |
||||
* @param d input to add. |
||||
*/ |
||||
|
||||
public void set(Input d) { |
||||
cursor[0] = d; |
||||
cursor[1] = cursor[0]; |
||||
cursor[2] = cursor[1]; |
||||
} |
||||
|
||||
public void setPressure(int pressure) { |
||||
//cursor[0].pressure = pressure;
|
||||
//cursor[1].pressure = pressure;
|
||||
//cursor[2].pressure = pressure;
|
||||
} |
||||
|
||||
/** |
||||
* Adds a new input for smoothing. |
||||
* |
||||
* @param d input to add. |
||||
*/ |
||||
|
||||
public void add(Input d) { |
||||
float dx = d.x - cursor[2].x; |
||||
float dy = d.y - cursor[2].y; |
||||
float dd = d.pressure - cursor[2].pressure; |
||||
|
||||
float h = (float)Math.hypot(dx, dy); |
||||
|
||||
float d2 = h - smoothness; |
||||
|
||||
if(d2 > 0) { |
||||
float sx = dx * d2 / h; |
||||
float sy = dy * d2 / h; |
||||
float sd = dd * d2 / h; |
||||
|
||||
cursor[0] = cursor[1]; |
||||
cursor[1] = cursor[2]; |
||||
cursor[2] = new Input(cursor[2].x + sx, cursor[2].y + sy, (int)(cursor[2].pressure + sd)); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Return a list of smoothed inputs. |
||||
* |
||||
* @return List of smoothed inputs. |
||||
*/ |
||||
|
||||
public List<Input> get() { |
||||
/* Create list for inputs. */ |
||||
List<Input> inputs = new ArrayList<Input>(); |
||||
|
||||
if(enabled) { |
||||
/* Segment cursor. */ |
||||
segment(inputs, cursor[0], cursor[1], cursor[2]); |
||||
} else { |
||||
inputs.add(cursor[2]); |
||||
} |
||||
|
||||
return inputs; |
||||
} |
||||
|
||||
public Input getIndex(int index) { |
||||
return cursor[index]; |
||||
} |
||||
|
||||
// uses a, b and c to interpolate inputs between b and c, store in inputs.
|
||||
void segment(List<Input> inputs, Input a, Input b, Input c) { |
||||
// difference AB & BC
|
||||
Input dab = a.difference(b); |
||||
Input dbc = b.difference(c); |
||||
|
||||
// magnitude AB & BC
|
||||
float mab = (float)Math.hypot(dab.x, dab.y); |
||||
float mbc = (float)Math.hypot(dbc.x, dbc.y); |
||||
|
||||
// orientation AB & BC
|
||||
float oab = (float)Math.acos(dab.x / mab) * (dab.y < 0 ? -1 : 1); |
||||
float obc = (float)Math.acos(dbc.x / mbc) * (dbc.y < 0 ? -1 : 1); |
||||
|
||||
// normalize
|
||||
if(obc - oab > Math.PI || obc - oab < -Math.PI) { |
||||
obc -= Math.PI * 2; |
||||
} |
||||
|
||||
// angle ABC
|
||||
float abc = Math.abs(obc - oab); |
||||
|
||||
// threshold of operation
|
||||
if(mbc * abc > threshold && abc < Math.PI / 2) { |
||||
// three-quarter of angle ABC
|
||||
float angle = (oab + (obc * 3)) / 4; |
||||
|
||||
// half of magnitude BC
|
||||
Input d = new Input(b.x + ((float)Math.cos(angle) * mbc / 2), b.y |
||||
+ ((float)Math.sin(angle) * mbc / 2), (b.pressure + c.pressure) / 2); |
||||
|
||||
segment(inputs, a, b, d); |
||||
inputs.add(d); |
||||
segment(inputs, b, d, c); |
||||
} else { |
||||
inputs.add(c); |
||||
} |
||||
} |
||||
|
||||
public static void setThreshold(float threshold) { |
||||
Smoother.threshold = threshold; |
||||
} |
||||
|
||||
public static float getThreshold() { |
||||
return threshold; |
||||
} |
||||
|
||||
public static void setSmoothness(float smoothness) { |
||||
Smoother.smoothness = smoothness; |
||||
} |
||||
|
||||
public static float getSmoothness() { |
||||
return smoothness; |
||||
} |
||||
|
||||
public static void setEnabled(boolean enabled) { |
||||
Smoother.enabled = enabled; |
||||
} |
||||
|
||||
public static boolean isEnabled() { |
||||
return enabled; |
||||
} |
||||
} |
@ -0,0 +1,56 @@ |
||||
package com.jotuntech.sketcher.client.command; |
||||
|
||||
import java.lang.reflect.InvocationTargetException; |
||||
import java.nio.ByteBuffer; |
||||
|
||||
import javax.swing.SwingUtilities; |
||||
|
||||
import com.jotuntech.sketcher.client.Client; |
||||
import com.jotuntech.sketcher.client.Command; |
||||
import com.jotuntech.sketcher.client.Connection; |
||||
import com.jotuntech.sketcher.common.Canvas; |
||||
import com.jotuntech.sketcher.common.Log; |
||||
import com.jotuntech.sketcher.common.User; |
||||
|
||||
public class CanvasCommand implements Command { |
||||
protected int width, height; |
||||
|
||||
public CanvasCommand() { |
||||
|
||||
} |
||||
|
||||
public CanvasCommand(int width, int height) { |
||||
this.width = width; |
||||
this.height = height; |
||||
} |
||||
|
||||
public int perform(final Client client, User user) { |
||||
try { |
||||
SwingUtilities.invokeAndWait(new Runnable() { |
||||
public void run() { |
||||
/** Create and set new canvas */ |
||||
client.setCanvas(new Canvas(width, height)); |
||||
Log.info("Created canvas."); |
||||
} |
||||
}); |
||||
} catch (InterruptedException e) { |
||||
Log.error(e); |
||||
} catch (InvocationTargetException e) { |
||||
Log.error(e); |
||||
} |
||||
|
||||
/** Return silently */ |
||||
return Connection.SEND_NONE; |
||||
} |
||||
|
||||
public void decode(ByteBuffer in) { |
||||
width = in.getInt(); |
||||
height = in.getInt(); |
||||
} |
||||
|
||||
public void encode(ByteBuffer out) { |
||||
out.putInt(width); |
||||
out.putInt(height); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,56 @@ |
||||
package com.jotuntech.sketcher.client.command; |
||||
|
||||
import java.nio.ByteBuffer; |
||||
|
||||
import com.jotuntech.sketcher.client.Client; |
||||
import com.jotuntech.sketcher.client.Command; |
||||
import com.jotuntech.sketcher.client.Connection; |
||||
import com.jotuntech.sketcher.common.BitmapLayer; |
||||
import com.jotuntech.sketcher.common.Layer; |
||||
import com.jotuntech.sketcher.common.User; |
||||
|
||||
public class CreateLayerCommand implements Command { |
||||
int layerKey; |
||||
int type; |
||||
String name; |
||||
|
||||
public CreateLayerCommand() { |
||||
|
||||
} |
||||
|
||||
public CreateLayerCommand(int layerKey, int type, String name) { |
||||
this.layerKey = layerKey; |
||||
this.type = type; |
||||
this.name = name; |
||||
} |
||||
|
||||
public int perform(Client client, User user) { |
||||
Layer l = new BitmapLayer(name); |
||||
client.getCanvas().getLayerMap().put(layerKey, l); |
||||
|
||||
if(user != null) { |
||||
client.getUserInterface().println(user.getName() + " created layer " + l.getName()); |
||||
} |
||||
|
||||
return Connection.SEND_ALL; |
||||
} |
||||
|
||||
public void decode(ByteBuffer in) { |
||||
layerKey = in.getInt(); |
||||
type = in.get(); |
||||
StringBuffer nameBuffer = new StringBuffer(); |
||||
while(in.remaining() >= 2) { |
||||
nameBuffer.append(in.getChar()); |
||||
} |
||||
name = nameBuffer.toString(); |
||||
} |
||||
|
||||
public void encode(ByteBuffer out) { |
||||
out.putInt(layerKey); |
||||
out.put((byte) type); |
||||
for(int i = 0; i < name.length(); i++) { |
||||
out.putChar(name.charAt(i)); |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,56 @@ |
||||
|
||||
package com.jotuntech.sketcher.client.command; |
||||
|
||||
import java.nio.ByteBuffer; |
||||
|
||||
import com.jotuntech.sketcher.client.Client; |
||||
import com.jotuntech.sketcher.client.Command; |
||||
import com.jotuntech.sketcher.client.Connection; |
||||
import com.jotuntech.sketcher.common.Input; |
||||
import com.jotuntech.sketcher.common.User; |
||||
|
||||
/** Cursor command. |
||||
* @author Thor Harald Johansen |
||||
* |
||||
*/ |
||||
public class CursorCommand implements Command { |
||||
Input input; |
||||
|
||||
/** Constructs an Cursor command. */ |
||||
public CursorCommand() { |
||||
|
||||
} |
||||
|
||||
/** Constructs an Cursor command. |
||||
* @param user Source user. |
||||
* @param input Cursor position */ |
||||
public CursorCommand(Input input) { |
||||
this.input = input; |
||||
} |
||||
|
||||
public int perform(Client client, User user) { |
||||
if(user == null) { |
||||
/** Signed in users only. */ |
||||
return Connection.SEND_NONE; |
||||
} |
||||
|
||||
if(user.isViewer()) { |
||||
return Connection.SEND_NONE; |
||||
} |
||||
|
||||
user.setCursor(input); |
||||
|
||||
/** Return and broadcast. */ |
||||
return Connection.SEND_OTHERS; |
||||
} |
||||
|
||||
public void decode(ByteBuffer in) { |
||||
input = new Input(); |
||||
input.decode(in); |
||||
} |
||||
|
||||
public void encode(ByteBuffer out) { |
||||
input.encode(out); |
||||
} |
||||
} |
||||
|
@ -0,0 +1,74 @@ |
||||
package com.jotuntech.sketcher.client.command; |
||||
|
||||
import java.awt.image.ImageObserver; |
||||
import java.nio.ByteBuffer; |
||||
import java.util.HashSet; |
||||
import java.util.Set; |
||||
|
||||
import com.jotuntech.sketcher.client.Client; |
||||
import com.jotuntech.sketcher.client.Command; |
||||
import com.jotuntech.sketcher.client.Connection; |
||||
import com.jotuntech.sketcher.common.Canvas; |
||||
import com.jotuntech.sketcher.common.Layer; |
||||
import com.jotuntech.sketcher.common.UndoEntry; |
||||
import com.jotuntech.sketcher.common.User; |
||||
|
||||
public class DeleteLayerCommand implements Command { |
||||
int layerKey; |
||||
|
||||
public DeleteLayerCommand() { |
||||
|
||||
} |
||||
|
||||
public DeleteLayerCommand(int layerKey) { |
||||
this.layerKey = layerKey; |
||||
} |
||||
|
||||
public int perform(Client client, User user) { |
||||
if(user == null) { |
||||
return Connection.SEND_NONE; |
||||
} |
||||
|
||||
if(user.isViewer()) { |
||||
return Connection.SEND_NONE; |
||||
} |
||||
|
||||
Canvas canvas = client.getCanvas(); |
||||
Layer layer = canvas.getLayerMap().remove(layerKey); |
||||
|
||||
if(layer == null) { |
||||
return Connection.SEND_NONE; |
||||
} |
||||
|
||||
// Remove references to layer from undo deque
|
||||
for(User u: client.getUserMap().values()) { |
||||
Set<UndoEntry> uers = new HashSet<UndoEntry>(); |
||||
for(UndoEntry ue : u.getUndoDeque()) { |
||||
if(ue.getLayer() == layer) { |
||||
uers.add(ue); |
||||
} |
||||
} |
||||
for(UndoEntry ue : uers) { |
||||
u.getUndoDeque().remove(ue); |
||||
} |
||||
} |
||||
|
||||
if(canvas.getImageObserver() != null) { |
||||
canvas.getImageObserver().imageUpdate(null, ImageObserver.ALLBITS, 0, 0, canvas.getWidth(), canvas.getHeight()); |
||||
} |
||||
|
||||
/** Notify the user */ |
||||
client.getUserInterface().println(user.getName() + " deleted layer " + layer.getName()); |
||||
|
||||
/** Return and broadcast. */ |
||||
return Connection.SEND_ALL; |
||||
} |
||||
|
||||
public void decode(ByteBuffer in) { |
||||
layerKey = in.getInt(); |
||||
} |
||||
|
||||
public void encode(ByteBuffer out) { |
||||
out.putInt(layerKey); |
||||
} |
||||
} |
@ -0,0 +1,82 @@ |
||||
package com.jotuntech.sketcher.client.command; |
||||
|
||||
import java.awt.AlphaComposite; |
||||
import java.nio.ByteBuffer; |
||||
|
||||
import com.jotuntech.sketcher.client.Client; |
||||
import com.jotuntech.sketcher.client.Command; |
||||
import com.jotuntech.sketcher.client.Connection; |
||||
import com.jotuntech.sketcher.common.BitmapLayer; |
||||
import com.jotuntech.sketcher.common.User; |
||||
import com.jotuntech.sketcher.common.filter.Filter; |
||||
|
||||
public class FilterCommand implements Command { |
||||
private Filter filter; |
||||
private int x, y, w, h; |
||||
float a, b, c; |
||||
|
||||
public FilterCommand() { |
||||
|
||||
} |
||||
|
||||
public FilterCommand(Filter filter, float x, float y, float w, float h, float a, float b, float c) { |
||||
this.filter = filter; |
||||
this.x = (int) x; |
||||
this.y = (int) y; |
||||
this.w = (int) w; |
||||
this.h = (int) h; |
||||
this.a = a; |
||||
this.b = b; |
||||
this.c = c; |
||||
} |
||||
|
||||
public int perform(Client client, User user) { |
||||
if(user == null) { |
||||
return Connection.SEND_NONE; |
||||
} |
||||
|
||||
BitmapLayer pl = (BitmapLayer) user.getPhantomLayer(); |
||||
filter.setSize(w, h); |
||||
filter.setParameterA(a); |
||||
user.getLayer().copyTo(pl, null, false, x, y, x, y, w, h); |
||||
pl.setAlphaRule(AlphaComposite.SRC); |
||||
pl.setOpacity(1); |
||||
pl.applyFilter(filter, user.getLayer().getImageObserver(), x, y, w, h); |
||||
return Connection.SEND_OTHERS; |
||||
} |
||||
|
||||
public void decode(ByteBuffer in) { |
||||
x = (int) in.getFloat(); |
||||
y = (int) in.getFloat(); |
||||
w = (int) in.getFloat(); |
||||
h = (int) in.getFloat(); |
||||
a = in.getFloat(); |
||||
b = in.getFloat(); |
||||
c = in.getFloat(); |
||||
|
||||
StringBuffer filterNameBuffer = new StringBuffer(); |
||||
while(in.remaining() >= 2) { |
||||
filterNameBuffer.append(in.getChar()); |
||||
} |
||||
String filterName = "com.jotuntech.sketcher.common.filter." + filterNameBuffer.toString(); |
||||
try { |
||||
filter = (Filter) Class.forName(filterName).newInstance(); |
||||
} catch (Throwable t) { |
||||
t.printStackTrace(); |
||||
} |
||||
} |
||||
|
||||
public void encode(ByteBuffer out) { |
||||
out.putFloat(x); |
||||
out.putFloat(y); |
||||
out.putFloat(w); |
||||
out.putFloat(h); |
||||
out.putFloat(a); |
||||
out.putFloat(b); |
||||
out.putFloat(c); |
||||
String filterName = filter.getClass().getSimpleName(); |
||||
for(int i = 0; i < filterName.length(); i++) { |
||||
out.putChar(filterName.charAt(i)); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,58 @@ |
||||
package com.jotuntech.sketcher.client.command; |
||||
|
||||
import java.nio.ByteBuffer; |
||||
|
||||
import com.jotuntech.sketcher.client.Client; |
||||
import com.jotuntech.sketcher.client.Command; |
||||
import com.jotuntech.sketcher.client.Connection; |
||||
import com.jotuntech.sketcher.client.UserInterface; |
||||
import com.jotuntech.sketcher.common.User; |
||||
|
||||
public class KickCommand implements Command { |
||||
int targetKey; |
||||
String reason; |
||||
|
||||
public KickCommand() { |
||||
|
||||
} |
||||
|
||||
public KickCommand(int targetKey, String reason) { |
||||
this.targetKey = targetKey; |
||||
this.reason = reason; |
||||
} |
||||
|
||||
public int perform(Client client, User user) { |
||||
if(user != null && user.isViewer()) { |
||||
return Connection.SEND_NONE; |
||||
} |
||||
|
||||
User targetUser = client.getUserMap().remove(targetKey); |
||||
client.getUserInterface().println(targetUser.getName() + " was kicked out (" + reason + ")"); |
||||
|
||||
if(targetUser == client.getConnection().getUser()) { |
||||
client.getConnection().setTimeOfDeath(System.currentTimeMillis()); |
||||
} |
||||
|
||||
if(client.isSoundEnabled()) { |
||||
UserInterface.AUDIO_KICK.play(); |
||||
} |
||||
|
||||
return Connection.SEND_NONE; |
||||
} |
||||
|
||||
public void decode(ByteBuffer in) { |
||||
targetKey = in.getInt(); |
||||
StringBuffer reasonBuffer = new StringBuffer(); |
||||
while(in.remaining() >= 2) { |
||||
reasonBuffer.append(in.getChar()); |
||||
} |
||||
reason = reasonBuffer.toString(); |
||||
} |
||||
|
||||
public void encode(ByteBuffer out) { |
||||
out.putInt(targetKey); |
||||
for(int i = 0; i < reason.length(); i++) { |
||||
out.putChar(reason.charAt(i)); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,66 @@ |
||||
package com.jotuntech.sketcher.client.command; |
||||
|
||||
import java.nio.ByteBuffer; |
||||
|
||||
import com.jotuntech.sketcher.client.Client; |
||||
import com.jotuntech.sketcher.client.Command; |
||||
import com.jotuntech.sketcher.client.Connection; |
||||
import com.jotuntech.sketcher.common.Canvas; |
||||
import com.jotuntech.sketcher.common.Layer; |
||||
import com.jotuntech.sketcher.common.User; |
||||
|
||||
public class LayerDataCommand implements Command { |
||||
int layerKey; |
||||
boolean phantom; |
||||
byte[] data; |
||||
|
||||
public LayerDataCommand() { |
||||
} |
||||
|
||||
public LayerDataCommand(int layerKey, boolean phantom, byte[] data) { |
||||
this.layerKey = layerKey; |
||||
this.phantom = phantom; |
||||
this.data = data; |
||||
} |
||||
|
||||
public int perform(Client client, User user) { |
||||
Canvas canvas = client.getCanvas(); |
||||
|
||||
if(canvas == null) { |
||||
return Connection.SEND_NONE; |
||||
} |
||||
|
||||
Layer layer; |
||||
|
||||
if(phantom) { |
||||
if(user == null) { |
||||
return Connection.SEND_NONE; |
||||
} |
||||
|
||||
layer = user.getPhantomLayer(); |
||||
} else { |
||||
layer = canvas.getLayerMap().get(layerKey); |
||||
|
||||
if(layer == null) { |
||||
return Connection.SEND_NONE; |
||||
} |
||||
} |
||||
|
||||
layer.decode(data); |
||||
|
||||
return Connection.SEND_NONE; |
||||
} |
||||
|
||||
public void decode(ByteBuffer in) { |
||||
layerKey = in.getInt(); |
||||
phantom = in.get() != 0; |
||||
data = new byte[in.remaining()]; |
||||
in.get(data); |
||||
} |
||||
|
||||
public void encode(ByteBuffer out) { |
||||
out.putInt(layerKey); |
||||
out.put((byte) (phantom ? 0xFF : 0x00)); |
||||
out.put(data); |
||||
} |
||||
} |
@ -0,0 +1,73 @@ |
||||
/** |
||||
* |
||||
*/ |
||||
package com.jotuntech.sketcher.client.command; |
||||
|
||||
import java.awt.AlphaComposite; |
||||
import java.nio.ByteBuffer; |
||||
|
||||
import com.jotuntech.sketcher.client.Client; |
||||
import com.jotuntech.sketcher.client.Command; |
||||
import com.jotuntech.sketcher.client.Connection; |
||||
import com.jotuntech.sketcher.common.Input; |
||||
import com.jotuntech.sketcher.common.Layer; |
||||
import com.jotuntech.sketcher.common.User; |
||||
|
||||
|
||||
public class LineCommand implements Command { |
||||
protected Input input; |
||||
|
||||
/** Constructs a Daub command */ |
||||
public LineCommand() { |
||||
|
||||
} |
||||
|
||||
/** Constructs a Daub command. |
||||
* @param user Source user. |
||||
* @param input Position of new daub. */ |
||||
public LineCommand(Input input) { |
||||
this.input = input; |
||||
} |
||||
|
||||
public int perform(Client client, User user) { |
||||
if(user != null && user.getLayer() != null && user.getBrush() != null) { |
||||
if(user.isViewer()) { |
||||
return Connection.SEND_NONE; |
||||
} |
||||
|
||||
Layer l = user.getPhantomLayer(); |
||||
l.setOpacity(Math.abs(user.getBrush().getOpacity()) / 255f); |
||||
if(user.getBrush().isLockTransparency()) { |
||||
l.setAlphaRule(AlphaComposite.SRC_ATOP); |
||||
} else if(user.getBrush().getOpacity() >= 0) { |
||||
l.setAlphaRule(AlphaComposite.SRC_OVER); |
||||
} else { |
||||
l.setAlphaRule(AlphaComposite.DST_OUT); |
||||
} |
||||
|
||||
/** Draw line */ |
||||
Input end = user.getPhantomLayer().line(user.getCursor(), input, user.getColor(), user.getBrush(), user.getLayer()); |
||||
|
||||
if(end != null) { |
||||
/** Move cursor on success */ |
||||
user.setCursor(end); |
||||
|
||||
/** Return and broadcast */ |
||||
return Connection.SEND_OTHERS; |
||||
} |
||||
} |
||||
|
||||
/** Return silently */ |
||||
return Connection.SEND_NONE; |
||||
} |
||||
|
||||
public void decode(ByteBuffer in) { |
||||
input = new Input(); |
||||
input.decode(in); |
||||
} |
||||
|
||||
public void encode(ByteBuffer out) { |
||||
input.encode(out); |
||||
} |
||||
} |
||||
|
@ -0,0 +1,45 @@ |
||||
package com.jotuntech.sketcher.client.command; |
||||
|
||||
import java.nio.ByteBuffer; |
||||
import java.util.Deque; |
||||
|
||||
import com.jotuntech.sketcher.client.Client; |
||||
import com.jotuntech.sketcher.client.Command; |
||||
import com.jotuntech.sketcher.client.Connection; |
||||
import com.jotuntech.sketcher.common.UndoEntry; |
||||
import com.jotuntech.sketcher.common.User; |
||||
|
||||
public class MergeCommand implements Command { |
||||
public int perform(Client client, User user) { |
||||
if(user == null || user.getLayer() == null) { |
||||
return Connection.SEND_NONE; |
||||
} |
||||
|
||||
if(user.isViewer()) { |
||||
return Connection.SEND_NONE; |
||||
} |
||||
|
||||
if(user.getPhantomLayer().isEmpty()) { |
||||
return Connection.SEND_NONE; |
||||
} |
||||
|
||||
UndoEntry undoEntry = new UndoEntry(); |
||||
undoEntry.setLayer(user.getLayer()); |
||||
undoEntry.setUndoData(user.getLayer().merge(user.getPhantomLayer())); |
||||
Deque<UndoEntry> undoDeque = user.getUndoDeque(); |
||||
undoDeque.addFirst(undoEntry); |
||||
while(undoDeque.size() > 10) { |
||||
undoDeque.removeLast(); |
||||
} |
||||
|
||||
return Connection.SEND_OTHERS; |
||||
} |
||||
|
||||
public void decode(ByteBuffer in) { |
||||
|
||||
} |
||||
|
||||
public void encode(ByteBuffer out) { |
||||
|
||||
} |
||||
} |
@ -0,0 +1,68 @@ |
||||
package com.jotuntech.sketcher.client.command; |
||||
|
||||
import java.awt.AlphaComposite; |
||||
import java.nio.ByteBuffer; |
||||
|
||||
import com.jotuntech.sketcher.client.Client; |
||||
import com.jotuntech.sketcher.client.Command; |
||||
import com.jotuntech.sketcher.client.Connection; |
||||
import com.jotuntech.sketcher.common.BitmapLayer; |
||||
import com.jotuntech.sketcher.common.Layer; |
||||
import com.jotuntech.sketcher.common.User; |
||||
|
||||
public class MoveCommand implements Command { |
||||
private float sx, sy, dx, dy, w, h; |
||||
|
||||
public MoveCommand() { |
||||
|
||||
} |
||||
|
||||
public MoveCommand(float sx, float sy, float dx, float dy, float w, float h) { |
||||
this.sx = sx; |
||||
this.sy = sy; |
||||
this.dx = dx; |
||||
this.dy = dy; |
||||
this.w = w; |
||||
this.h = h; |
||||
} |
||||
|
||||
public int perform(Client client, User user) { |
||||
if(user == null || user.getLayer() == null) { |
||||
return Connection.SEND_NONE; |
||||
} |
||||
|
||||
if(user.isViewer()) { |
||||
return Connection.SEND_NONE; |
||||
} |
||||
|
||||
if(user.getPhantomLayer().isEmpty()) { |
||||
Layer pl = (BitmapLayer) user.getPhantomLayer(); |
||||
pl.setAlphaRule(AlphaComposite.SRC_OVER); |
||||
pl.setOpacity(1); |
||||
user.getLayer().copyTo(pl, null, false, sx, sy, dx, dy, w, h); |
||||
} else { |
||||
user.getPhantomLayer().copyTo(user.getPhantomLayer(), user.getLayer().getImageObserver(), true, sx, sy, dx, dy, w, h); |
||||
user.getPhantomLayer().clean(); |
||||
} |
||||
|
||||
return Connection.SEND_OTHERS; |
||||
} |
||||
|
||||
public void decode(ByteBuffer in) { |
||||
sx = in.getFloat(); |
||||
sy = in.getFloat(); |
||||
dx = in.getFloat(); |
||||
dy = in.getFloat(); |
||||
w = in.getFloat(); |
||||
h = in.getFloat(); |
||||
} |
||||
|
||||
public void encode(ByteBuffer out) { |
||||
out.putFloat(sx); |
||||
out.putFloat(sy); |
||||
out.putFloat(dx); |
||||
out.putFloat(dy); |
||||
out.putFloat(w); |
||||
out.putFloat(h); |
||||
} |
||||
} |
@ -0,0 +1,32 @@ |
||||
package com.jotuntech.sketcher.client.command; |
||||
|
||||
import java.nio.ByteBuffer; |
||||
|
||||
import com.jotuntech.sketcher.client.Client; |
||||
import com.jotuntech.sketcher.client.Command; |
||||
import com.jotuntech.sketcher.client.Connection; |
||||
import com.jotuntech.sketcher.common.User; |
||||
|
||||
public class PingCommand implements Command { |
||||
private long timestamp; |
||||
|
||||
public PingCommand() { |
||||
this.timestamp = System.currentTimeMillis(); |
||||
} |
||||
|
||||
public PingCommand(long timestamp) { |
||||
this.timestamp = timestamp; |
||||
} |
||||
|
||||
public int perform(Client client, User user) { |
||||
return Connection.SEND_NONE; |
||||
} |
||||
|
||||
public void decode(ByteBuffer in) { |
||||
timestamp = in.getLong(); |
||||
} |
||||
|
||||
public void encode(ByteBuffer out) { |
||||
out.putLong(timestamp); |
||||
} |
||||
} |
@ -0,0 +1,62 @@ |
||||
package com.jotuntech.sketcher.client.command; |
||||
|
||||
import java.nio.ByteBuffer; |
||||
|
||||
import com.jotuntech.sketcher.client.Client; |
||||
import com.jotuntech.sketcher.client.Command; |
||||
import com.jotuntech.sketcher.client.Connection; |
||||
import com.jotuntech.sketcher.client.UserInterface; |
||||
import com.jotuntech.sketcher.common.User; |
||||
|
||||
public class SayCommand implements Command { |
||||
boolean isAction; |
||||
String text; |
||||
|
||||
public SayCommand() { |
||||
|
||||
} |
||||
|
||||
public SayCommand(String text, boolean isAction) { |
||||
this.isAction = isAction; |
||||
this.text = text; |
||||
} |
||||
|
||||
public int perform(Client client, User user) { |
||||
if(user == null) { |
||||
return Connection.SEND_NONE; |
||||
} |
||||
|
||||
if(user.isViewer()) { |
||||
return Connection.SEND_NONE; |
||||
} |
||||
|
||||
|
||||
if(isAction) { |
||||
client.getUserInterface().println("\u2022 " + user.getName() + text); |
||||
} else { |
||||
client.getUserInterface().println("<" + user.getName() + "> " + text); |
||||
} |
||||
|
||||
if(client.isSoundEnabled()) { |
||||
UserInterface.AUDIO_CHAT.play(); |
||||
} |
||||
|
||||
return Connection.SEND_NONE; |
||||
} |
||||
|
||||
public void decode(ByteBuffer in) { |
||||
isAction = in.get() != 0 ? true : false; |
||||
StringBuffer textBuffer = new StringBuffer(); |
||||
while(in.remaining() >= 2) { |
||||
textBuffer.append(in.getChar()); |
||||
} |
||||
text = textBuffer.toString(); |
||||
} |
||||
|
||||
public void encode(ByteBuffer out) { |
||||
out.put((byte) (isAction ? 0xFF : 0x00)); |
||||
for(int i = 0; i < text.length(); i++) { |
||||
out.putChar(text.charAt(i)); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,41 @@ |
||||
package com.jotuntech.sketcher.client.command; |
||||
|
||||
import java.nio.ByteBuffer; |
||||
|
||||
import com.jotuntech.sketcher.client.Client; |
||||
import com.jotuntech.sketcher.client.Command; |
||||
import com.jotuntech.sketcher.client.Connection; |
||||
import com.jotuntech.sketcher.common.User; |
||||
|
||||
public class ServerMessageCommand implements Command { |
||||
String text; |
||||
|
||||
public ServerMessageCommand() { |
||||
} |
||||
|
||||
public ServerMessageCommand(String text) { |
||||
this.text = text; |
||||
} |
||||
|
||||
public int perform(Client client, User user) { |
||||
/** Notify the user */ |
||||
client.getUserInterface().println(text); |
||||
|
||||
/** Return silently. */ |
||||
return Connection.SEND_NONE; |
||||
} |
||||
|
||||
public void decode(ByteBuffer in) { |
||||
StringBuffer textBuffer = new StringBuffer(); |
||||
while(in.remaining() >= 2) { |
||||
textBuffer.append(in.getChar()); |
||||
} |
||||
text = textBuffer.toString(); |
||||
} |
||||
|
||||
public void encode(ByteBuffer out) { |
||||
for(int i = 0; i < text.length(); i++) { |
||||
out.putChar(text.charAt(i)); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,57 @@ |
||||
package com.jotuntech.sketcher.client.command; |
||||
|
||||
import java.nio.ByteBuffer; |
||||
|
||||
import com.jotuntech.sketcher.client.Client; |
||||
import com.jotuntech.sketcher.client.Command; |
||||
import com.jotuntech.sketcher.client.Connection; |
||||
import com.jotuntech.sketcher.client.Editor; |
||||
import com.jotuntech.sketcher.common.Brush; |
||||
import com.jotuntech.sketcher.common.User; |
||||
|
||||
public class SetBrushCommand implements Command { |
||||
Brush brush; |
||||
|
||||
public SetBrushCommand() { |
||||
|
||||
} |
||||
|
||||
public SetBrushCommand(Brush brush) { |
||||
this.brush = brush; |
||||
} |
||||
|
||||
public int perform(Client client, User user) { |
||||
if(user == null) { |
||||
/** Signed in users only */ |
||||
return Connection.SEND_NONE; |
||||
} |
||||
|
||||
if(user.isViewer()) { |
||||
return Connection.SEND_NONE; |
||||
} |
||||
|
||||
Brush oldBrush = user.getBrush(); |
||||
user.setBrush(brush); |
||||
|
||||
if(user == client.getConnection().getUser()) { |
||||
/** Update the GUI */ |
||||
client.getUserInterface().updateBrushSliders(); |
||||
|
||||
if(oldBrush == null) { |
||||
/* Enable editor when first brush is selected */ |
||||
client.getUserInterface().getEditor().setState(Editor.State.DRAW_HOVER); |
||||
} |
||||
} |
||||
|
||||
return Connection.SEND_OTHERS; |
||||
} |
||||
|
||||
public void decode(ByteBuffer in) { |
||||
brush = new Brush(); |
||||
brush.decode(in); |
||||
} |
||||
|
||||
public void encode(ByteBuffer out) { |
||||
brush.encode(out); |
||||
} |
||||
} |
@ -0,0 +1,53 @@ |
||||
package com.jotuntech.sketcher.client.command; |
||||
|
||||
import java.nio.ByteBuffer; |
||||
|
||||
import com.jotuntech.sketcher.client.Client; |
||||
import com.jotuntech.sketcher.client.Command; |
||||
import com.jotuntech.sketcher.client.Connection; |
||||
import com.jotuntech.sketcher.common.User; |
||||
|
||||
public class SetColorCommand implements Command { |
||||
static boolean firstWhite = true; |
||||
int color; |
||||
|
||||
public SetColorCommand() { |
||||
} |
||||
|
||||
public SetColorCommand(int color) { |
||||
this.color = color; |
||||
} |
||||
|
||||
public int perform(Client client, User user) { |
||||
if(color == 0xFFFFFF && user == client.getConnection().getUser() && client.getConnection().getUser().getBrush() != null && client.getConnection().getUser().getBrush().getOpacity() > 0 && firstWhite) { |
||||
client.getUserInterface().println("Use eraser unless drawing white. :-)"); |
||||
firstWhite = false; |
||||
} |
||||
|
||||
return perform(user) ? Connection.SEND_OTHERS : Connection.SEND_NONE; |
||||
} |
||||
|
||||
private boolean perform(User user) { |
||||
if(user == null) { |
||||
/** Signed in users only */ |
||||
return false; |
||||
} |
||||
|
||||
if(user.isViewer()) { |
||||
return false; |
||||
} |
||||
|
||||
user.setColor(color); |
||||
|
||||
return true; |
||||
} |
||||
|
||||
public void decode(ByteBuffer in) { |
||||
color = in.getInt(); |
||||
} |
||||
|
||||
public void encode(ByteBuffer out) { |
||||
out.putInt(color); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,64 @@ |
||||
package com.jotuntech.sketcher.client.command; |
||||
|
||||
import java.nio.ByteBuffer; |
||||
|
||||
import com.jotuntech.sketcher.client.Client; |
||||
import com.jotuntech.sketcher.client.Command; |
||||
import com.jotuntech.sketcher.client.Connection; |
||||
import com.jotuntech.sketcher.common.Canvas; |
||||
import com.jotuntech.sketcher.common.Layer; |
||||
import com.jotuntech.sketcher.common.User; |
||||
|
||||
public class SetLayerCommand implements Command { |
||||
Integer layerKey; |
||||
|
||||
public SetLayerCommand() { |
||||
} |
||||
|
||||
public SetLayerCommand(Integer layerKey) { |
||||
this.layerKey = layerKey; |
||||
} |
||||
|
||||
public int perform(Client client, User user) { |
||||
if(user == null) { |
||||
return Connection.SEND_NONE; |
||||
} |
||||
|
||||
if(user.isViewer()) { |
||||
return Connection.SEND_NONE; |
||||
} |
||||
|
||||
Canvas canvas = client.getCanvas(); |
||||
|
||||
if(canvas == null) { |
||||
return Connection.SEND_NONE; |
||||
} |
||||
|
||||
Layer layer = canvas.getLayerMap().get(layerKey); |
||||
|
||||
if(layer == null) { |
||||
return Connection.SEND_NONE; |
||||
} |
||||
|
||||
if(user.getLayer() == layer) { |
||||
return Connection.SEND_NONE; |
||||
} |
||||
|
||||
user.setLayer(layer); |
||||
|
||||
if(user == client.getConnection().getUser()) { |
||||
client.getUserInterface().updateLayer(); |
||||
} |
||||
|
||||
return Connection.SEND_OTHERS; |
||||
} |
||||
|
||||
|
||||
public void decode(ByteBuffer in) { |
||||
layerKey = in.getInt(); |
||||
} |
||||
|
||||
public void encode(ByteBuffer out) { |
||||
out.putInt(layerKey); |
||||
} |
||||
} |
@ -0,0 +1,96 @@ |
||||
/** |
||||
* |
||||
*/ |
||||
package com.jotuntech.sketcher.client.command; |
||||
|
||||
import java.nio.ByteBuffer; |
||||
|
||||
import com.jotuntech.sketcher.client.Client; |
||||
import com.jotuntech.sketcher.client.Command; |
||||
import com.jotuntech.sketcher.client.Connection; |
||||
import com.jotuntech.sketcher.client.UserInterface; |
||||
import com.jotuntech.sketcher.common.User; |
||||
|
||||
/** |
||||
* @author Thor Harald Johansen |
||||
* |
||||
*/ |
||||
public class SignInCommand implements Command { |
||||
Integer peerKey; |
||||
String login; |
||||
String password; |
||||
boolean viewer; |
||||
|
||||
public SignInCommand() { |
||||
|
||||
} |
||||
|
||||
public SignInCommand(Integer peerKey, String login, String password, boolean viewer) { |
||||
this.peerKey = peerKey; |
||||
this.login = login; |
||||
this.password = password; |
||||
this.viewer = viewer; |
||||
} |
||||
|
||||
public void decode(ByteBuffer in) { |
||||
peerKey = in.getInt(); |
||||
viewer = in.get() != 0; |
||||
int loginLength = (in.get() & 0xFF) + 1; |
||||
StringBuffer loginBuffer = new StringBuffer(); |
||||
for(int i = 0; i < loginLength; i++) { |
||||
loginBuffer.append(in.getChar()); |
||||
} |
||||
login = loginBuffer.toString(); |
||||
|
||||
StringBuffer passwordBuffer = new StringBuffer(); |
||||
while(in.remaining() >= 2) { |
||||
passwordBuffer.append(in.getChar()); |
||||
} |
||||
password = passwordBuffer.toString(); |
||||
} |
||||
|
||||
public void encode(ByteBuffer out) { |
||||
out.putInt(peerKey); |
||||
out.put((byte) (viewer ? 0xFF : 0x00)); |
||||
out.put((byte) (login.length() - 1)); |
||||
for(int i = 0; i < login.length(); i++) { |
||||
out.putChar(login.charAt(i)); |
||||
} |
||||
for(int i = 0; i < password.length(); i++) { |
||||
out.putChar(password.charAt(i)); |
||||
} |
||||
} |
||||
|
||||
public int perform(Client client, User user) { |
||||
/** Create user */ |
||||
user = new User(login); |
||||
|
||||
/** Set viewer flag */ |
||||
user.setViewer(viewer); |
||||
|
||||
/** Put user in client user map */ |
||||
client.getUserMap().put(peerKey, user); |
||||
|
||||
if(client.getConnection().getUser() == null) { |
||||
/** Assign user to client */ |
||||
client.getConnection().setUser(user); |
||||
|
||||
/** Select a default brush */ |
||||
client.getUserInterface().getBrushButtons()[0].doClick(); |
||||
} |
||||
|
||||
/** Add canvas as phantom layer listener */ |
||||
user.getPhantomLayer().addImageObserver(client.getCanvas()); |
||||
|
||||
/** Print message */ |
||||
client.getUserInterface().println(user.getName() + " has signed in"); |
||||
|
||||
/** Play audio */ |
||||
if(client.isSoundEnabled()) { |
||||
UserInterface.AUDIO_SIGN_IN.play(); |
||||
} |
||||
|
||||
/** Return silently */ |
||||
return Connection.SEND_NONE; |
||||
} |
||||
} |
@ -0,0 +1,49 @@ |
||||
package com.jotuntech.sketcher.client.command; |
||||
|
||||
import java.nio.ByteBuffer; |
||||
|
||||
import com.jotuntech.sketcher.client.Client; |
||||
import com.jotuntech.sketcher.client.Command; |
||||
import com.jotuntech.sketcher.client.Connection; |
||||
import com.jotuntech.sketcher.client.UserInterface; |
||||
import com.jotuntech.sketcher.common.User; |
||||
|
||||
public class SignOutCommand implements Command { |
||||
String message; |
||||
|
||||
public SignOutCommand() { |
||||
message = new String(); |
||||
} |
||||
|
||||
public SignOutCommand(String message) { |
||||
this.message = message; |
||||
} |
||||
|
||||
public int perform(Client client, User user) { |
||||
client.getUserMap().removeByValue(user); |
||||
|
||||
if(user != null) { |
||||
client.getUserInterface().println(user.getName() + " has disconnected (" + message + ")"); |
||||
} |
||||
|
||||
if(client.isSoundEnabled()) { |
||||
UserInterface.AUDIO_SIGN_OUT.play(); |
||||
} |
||||
|
||||
return Connection.SEND_NONE; |
||||
} |
||||
|
||||
public void decode(ByteBuffer in) { |
||||
StringBuffer messageBuffer = new StringBuffer(); |
||||
while(in.remaining() >= 2) { |
||||
messageBuffer.append(in.getChar()); |
||||
} |
||||
message = messageBuffer.toString(); |
||||
} |
||||
|
||||
public void encode(ByteBuffer out) { |
||||
for(int i = 0; i < message.length(); i++) { |
||||
out.putChar(message.charAt(i)); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,35 @@ |
||||
package com.jotuntech.sketcher.client.command; |
||||
|
||||
import java.nio.ByteBuffer; |
||||
import java.util.Deque; |
||||
|
||||
import com.jotuntech.sketcher.client.Client; |
||||
import com.jotuntech.sketcher.client.Command; |
||||
import com.jotuntech.sketcher.client.Connection; |
||||
import com.jotuntech.sketcher.common.UndoEntry; |
||||
import com.jotuntech.sketcher.common.User; |
||||
|
||||
public class UndoCommand implements Command { |
||||
|
||||
public int perform(Client client, User user) { |
||||
if(user.isViewer()) { |
||||
return Connection.SEND_NONE; |
||||
} |
||||
|
||||
Deque<UndoEntry> undoDeque = user.getUndoDeque(); |
||||
if(undoDeque.size() == 0) { |
||||
return Connection.SEND_NONE; |
||||
} |
||||
UndoEntry undoEntry = undoDeque.removeFirst(); |
||||
undoEntry.getLayer().undo(undoEntry.getUndoData()); |
||||
return Connection.SEND_OTHERS; |
||||
} |
||||
|
||||
public void decode(ByteBuffer in) { |
||||
|
||||
} |
||||
|
||||
public void encode(ByteBuffer out) { |
||||
|
||||
} |
||||
} |
@ -0,0 +1,45 @@ |
||||
package com.jotuntech.sketcher.client.command; |
||||
|
||||
import java.nio.ByteBuffer; |
||||
import java.util.Deque; |
||||
|
||||
import com.jotuntech.sketcher.client.Client; |
||||
import com.jotuntech.sketcher.client.Command; |
||||
import com.jotuntech.sketcher.client.Connection; |
||||
import com.jotuntech.sketcher.common.BitmapUndoData; |
||||
import com.jotuntech.sketcher.common.UndoEntry; |
||||
import com.jotuntech.sketcher.common.User; |
||||
|
||||
public class UndoDataCommand implements Command { |
||||
byte[] data; |
||||
|
||||
public int perform(Client client, User user) { |
||||
if(user == null) { |
||||
return Connection.SEND_NONE; |
||||
} |
||||
|
||||
if(user.isViewer()) { |
||||
return Connection.SEND_NONE; |
||||
} |
||||
|
||||
Deque<UndoEntry> undoDeque = user.getUndoDeque(); |
||||
|
||||
if(undoDeque.size() == 0) { |
||||
return Connection.SEND_NONE; |
||||
} |
||||
|
||||
BitmapUndoData bud = (BitmapUndoData) undoDeque.getLast().getUndoData(); |
||||
bud.decode(data); |
||||
|
||||
return Connection.SEND_NONE; |
||||
} |
||||
|
||||
public void decode(ByteBuffer in) { |
||||
data = new byte[in.remaining()]; |
||||
in.get(data); |
||||
} |
||||
|
||||
public void encode(ByteBuffer out) { |
||||
out.put(data); |
||||
} |
||||
} |
@ -0,0 +1,46 @@ |
||||
package com.jotuntech.sketcher.client.command; |
||||
|
||||
import java.nio.ByteBuffer; |
||||
|
||||
import com.jotuntech.sketcher.client.Client; |
||||
import com.jotuntech.sketcher.client.Command; |
||||
import com.jotuntech.sketcher.common.BitmapUndoData; |
||||
import com.jotuntech.sketcher.common.Layer; |
||||
import com.jotuntech.sketcher.common.UndoEntry; |
||||
import com.jotuntech.sketcher.common.User; |
||||
import com.jotuntech.sketcher.server.Connection; |
||||
|
||||
public class UndoEntryCommand implements Command { |
||||
Integer layerKey; |
||||
|
||||
public int perform(Client client, User user) { |
||||
if(user == null) { |
||||
return Connection.SEND_NONE; |
||||
} |
||||
|
||||
if(user.isViewer()) { |
||||
return Connection.SEND_NONE; |
||||
} |
||||
|
||||
Layer layer = client.getCanvas().getLayerMap().get(layerKey); |
||||
|
||||
if(layer == null) { |
||||
return Connection.SEND_NONE; |
||||
} |
||||
|
||||
UndoEntry undoEntry = new UndoEntry(); |
||||
undoEntry.setLayer(layer); |
||||
undoEntry.setUndoData(new BitmapUndoData()); |
||||
user.getUndoDeque().addLast(undoEntry); |
||||
|
||||
return Connection.SEND_NONE; |
||||
} |
||||
|
||||
public void decode(ByteBuffer in) { |
||||
layerKey = in.getInt(); |
||||
} |
||||
|
||||
public void encode(ByteBuffer out) { |
||||
out.putInt(layerKey); |
||||
} |
||||
} |
@ -0,0 +1,37 @@ |
||||
package com.jotuntech.sketcher.client.command; |
||||
|
||||
import java.nio.ByteBuffer; |
||||
|
||||
import com.jotuntech.sketcher.client.Client; |
||||
import com.jotuntech.sketcher.client.Command; |
||||
import com.jotuntech.sketcher.client.Connection; |
||||
import com.jotuntech.sketcher.common.User; |
||||
|
||||
public class VoiceCommand implements Command { |
||||
boolean voiceEnabled; |
||||
|
||||
public VoiceCommand() { |
||||
|
||||
} |
||||
|
||||
public VoiceCommand(boolean voiceEnabled) { |
||||
this.voiceEnabled = voiceEnabled; |
||||
} |
||||
|
||||
public int perform(Client client, User user) { |
||||
if(user.isViewer()) { |
||||
return Connection.SEND_NONE; |
||||
} |
||||
|
||||
client.setVoiceEnabled(voiceEnabled); |
||||
return Connection.SEND_OTHERS; |
||||
} |
||||
|
||||
public void decode(ByteBuffer in) { |
||||
voiceEnabled = in.get() != 0; |
||||
} |
||||
|
||||
public void encode(ByteBuffer out) { |
||||
out.put((byte) (voiceEnabled ? 0xFF : 0x00)); |
||||
} |
||||
} |
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 53 KiB |
After Width: | Height: | Size: 302 B |
After Width: | Height: | Size: 348 B |
After Width: | Height: | Size: 781 B |
After Width: | Height: | Size: 489 B |
After Width: | Height: | Size: 317 B |
After Width: | Height: | Size: 507 B |
After Width: | Height: | Size: 105 B |
After Width: | Height: | Size: 274 B |
After Width: | Height: | Size: 44 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 913 B |
After Width: | Height: | Size: 216 B |
After Width: | Height: | Size: 297 B |
After Width: | Height: | Size: 324 B |
After Width: | Height: | Size: 128 B |
After Width: | Height: | Size: 342 B |
After Width: | Height: | Size: 592 B |
After Width: | Height: | Size: 422 B |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 308 B |
After Width: | Height: | Size: 153 B |
After Width: | Height: | Size: 244 B |
After Width: | Height: | Size: 622 B |
After Width: | Height: | Size: 265 B |
After Width: | Height: | Size: 309 B |
After Width: | Height: | Size: 398 B |
After Width: | Height: | Size: 292 B |
After Width: | Height: | Size: 306 B |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 416 B |
After Width: | Height: | Size: 614 B |
After Width: | Height: | Size: 244 B |
After Width: | Height: | Size: 335 B |
After Width: | Height: | Size: 436 B |
After Width: | Height: | Size: 840 B |
After Width: | Height: | Size: 238 B |
After Width: | Height: | Size: 383 B |
After Width: | Height: | Size: 982 B |
After Width: | Height: | Size: 293 B |
After Width: | Height: | Size: 243 B |