@ -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 |