Переглянути джерело

Adding all the things
- GUI Obtains data for the first page, less skills and attacks
- Dynamically add panes for each class the character has levels in
- Rolling own observer pattern, removes delta/observation responsibility from the Model

Sam Jaffe 9 роки тому
коміт
37bbe6b661
58 змінених файлів з 3798 додано та 0 видалено
  1. 3 0
      .gitignore
  2. 46 0
      pom.xml
  3. 52 0
      resources/Potato.json
  4. 30 0
      resources/classes/Bard.json
  5. 43 0
      resources/classes/Cleric.json
  6. 15 0
      resources/spells/default.json
  7. 17 0
      src/org/leumasjaffe/charsheet/Test.java
  8. 51 0
      src/org/leumasjaffe/charsheet/entity/AbilityScores.java
  9. 5 0
      src/org/leumasjaffe/charsheet/entity/Alignment.java
  10. 23 0
      src/org/leumasjaffe/charsheet/entity/AttackQuality.java
  11. 17 0
      src/org/leumasjaffe/charsheet/entity/DDActionType.java
  12. 77 0
      src/org/leumasjaffe/charsheet/entity/DDCharacter.java
  13. 67 0
      src/org/leumasjaffe/charsheet/entity/DDCharacterClass.java
  14. 41 0
      src/org/leumasjaffe/charsheet/entity/DDClass.java
  15. 14 0
      src/org/leumasjaffe/charsheet/entity/DDFeature.java
  16. 5 0
      src/org/leumasjaffe/charsheet/entity/Gender.java
  17. 27 0
      src/org/leumasjaffe/charsheet/entity/HitPoints.java
  18. 19 0
      src/org/leumasjaffe/charsheet/entity/SaveQuality.java
  19. 22 0
      src/org/leumasjaffe/charsheet/entity/SizeClass.java
  20. 80 0
      src/org/leumasjaffe/charsheet/entity/magic/Area.java
  21. 73 0
      src/org/leumasjaffe/charsheet/entity/magic/DDSpell.java
  22. 52 0
      src/org/leumasjaffe/charsheet/entity/magic/DDSpellFactory.java
  23. 42 0
      src/org/leumasjaffe/charsheet/entity/magic/DDSpellList.java
  24. 18 0
      src/org/leumasjaffe/charsheet/entity/magic/DDSpellbook.java
  25. 86 0
      src/org/leumasjaffe/charsheet/entity/magic/Range.java
  26. 6 0
      src/org/leumasjaffe/charsheet/entity/magic/Source.java
  27. 30 0
      src/org/leumasjaffe/charsheet/entity/viewable/IntValue.java
  28. 28 0
      src/org/leumasjaffe/charsheet/entity/viewable/ObjectValue.java
  29. 30 0
      src/org/leumasjaffe/charsheet/entity/viewable/StringValue.java
  30. 7 0
      src/org/leumasjaffe/charsheet/observer/Observable.java
  31. 45 0
      src/org/leumasjaffe/charsheet/observer/ObservableController.java
  32. 42 0
      src/org/leumasjaffe/charsheet/observer/ObservableListener.java
  33. 37 0
      src/org/leumasjaffe/charsheet/observer/ObserverDispatch.java
  34. 6 0
      src/org/leumasjaffe/charsheet/observer/Subscriber.java
  35. 16 0
      src/org/leumasjaffe/charsheet/observer/helper/AbilModStringify.java
  36. 10 0
      src/org/leumasjaffe/charsheet/observer/helper/Helper.java
  37. 13 0
      src/org/leumasjaffe/charsheet/observer/helper/IntValueHelper.java
  38. 21 0
      src/org/leumasjaffe/charsheet/observer/helper/IntValueStringify.java
  39. 11 0
      src/org/leumasjaffe/charsheet/observer/helper/StringValueHelper.java
  40. 9 0
      src/org/leumasjaffe/charsheet/observer/helper/Stringify.java
  41. 66 0
      src/org/leumasjaffe/charsheet/view/ClassTab.java
  42. 154 0
      src/org/leumasjaffe/charsheet/view/D20Sheet.java
  43. 25 0
      src/org/leumasjaffe/charsheet/view/StringHelper.java
  44. 189 0
      src/org/leumasjaffe/charsheet/view/SummaryTab.java
  45. 98 0
      src/org/leumasjaffe/charsheet/view/summary/AbilityBox.java
  46. 82 0
      src/org/leumasjaffe/charsheet/view/summary/AbilityLine.java
  47. 70 0
      src/org/leumasjaffe/charsheet/view/summary/AbilityPanel.java
  48. 349 0
      src/org/leumasjaffe/charsheet/view/summary/ArmorLine.java
  49. 251 0
      src/org/leumasjaffe/charsheet/view/summary/AttackLine.java
  50. 65 0
      src/org/leumasjaffe/charsheet/view/summary/DamageReductionLine.java
  51. 387 0
      src/org/leumasjaffe/charsheet/view/summary/DescriptionPanel.java
  52. 172 0
      src/org/leumasjaffe/charsheet/view/summary/HealthLine.java
  53. 142 0
      src/org/leumasjaffe/charsheet/view/summary/InitiativeLine.java
  54. 251 0
      src/org/leumasjaffe/charsheet/view/summary/ResistanceLine.java
  55. 68 0
      src/org/leumasjaffe/charsheet/view/summary/ResistancePanel.java
  56. 64 0
      src/org/leumasjaffe/charsheet/view/summary/SpellResistanceLine.java
  57. 83 0
      src/org/leumasjaffe/event/AnyActionDocumentListener.java
  58. 46 0
      src/org/leumasjaffe/graphics/NumberTextField.java

+ 3 - 0
.gitignore

@@ -0,0 +1,3 @@
+target/
+jgoodies-forms-1.8.0-sources.jar
+jgoodies-forms-1.8.0.jar

+ 46 - 0
pom.xml

@@ -0,0 +1,46 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>samjaffe</groupId>
+  <artifactId>d20-charsheet</artifactId>
+  <version>0.0.1-SNAPSHOT</version>
+  <build>
+    <sourceDirectory>src</sourceDirectory>
+    <plugins>
+      <plugin>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <version>3.3</version>
+        <configuration>
+          <source>1.8</source>
+          <target>1.8</target>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+  <dependencies>
+  	<dependency>
+  		<groupId>org.projectlombok</groupId>
+  		<artifactId>lombok</artifactId>
+  		<version>1.16.8</version>
+  	</dependency>
+  	<dependency>
+  		<groupId>com.fasterxml.jackson.core</groupId>
+  		<artifactId>jackson-annotations</artifactId>
+  		<version>2.7.3</version>
+  	</dependency>
+  	<dependency>
+  		<groupId>com.fasterxml.jackson.core</groupId>
+  		<artifactId>jackson-databind</artifactId>
+  		<version>2.7.3</version>
+  	</dependency>
+  	<dependency>
+  		<groupId>com.google.guava</groupId>
+  		<artifactId>guava</artifactId>
+  		<version>19.0</version>
+  	</dependency>
+  	<dependency>
+  		<groupId>com.fasterxml.jackson.datatype</groupId>
+  		<artifactId>jackson-datatype-jdk8</artifactId>
+  		<version>2.7.3</version>
+  	</dependency>
+  </dependencies>
+</project>

+ 52 - 0
resources/Potato.json

@@ -0,0 +1,52 @@
+{
+  "name":"Potato",
+  "player":"Corinne Kennedy",
+  "classes":[
+    {
+      "level":3,
+      "name":"Cleric"
+    },
+    {
+      "level":2,
+      "name":"Bard"
+    }
+  ],
+  "race":"Half-Elemental (E)",
+  "alignment":"TN",
+  "deity":"Obad-Hai",
+  "size":"M",
+  "age":"25",
+  "gender":"F",
+  "height":"5'0\"",
+  "weight":200,
+  "eyes":"Green",
+  "hair":"Plants",
+  "skin":"Earthy",
+  
+  "health":{
+    "total":30,
+    "rolled":20,
+    "current":30,
+    "temp":0,
+    "nonlethal":0
+  },
+  
+  "abilities":{
+    "base":{
+      "str":14,
+      "dex":10,
+      "con":15,
+      "int":12,
+      "wis":17,
+      "cha":11
+    },
+    "temp":{
+      "str":-1,
+      "dex":-1,
+      "con":-1,
+      "int":-1,
+      "wis":-1,
+      "cha":-1
+    }
+  }
+}

+ 30 - 0
resources/classes/Bard.json

@@ -0,0 +1,30 @@
+{
+  "name":"Bard",
+  "bab":"AVERAGE",
+  "fort":"POOR",
+  "ref":"GOOD",
+  "will":"GOOD",
+  "features":[
+    [
+      {"name":"Bardic Music"},
+      {"name":"Bardic Knowledge"},
+      {"name":"Countersong"},
+      {"name":"Fascinate"},
+      {"name":"Inspire Courage +1"}
+    ],
+    []
+  ],
+  "spells":{
+    "group":"ARCANE",
+    "known":[
+      [4],
+      [5, 2]
+    ],
+    "perDay":[
+      [2],
+      [3, 0]
+    ],
+    "spellList":[
+    ]
+  }
+}

+ 43 - 0
resources/classes/Cleric.json

@@ -0,0 +1,43 @@
+{
+  "name":"Cleric",
+  "bab":"AVERAGE",
+  "fort":"GOOD",
+  "ref":"POOR",
+  "will":"GOOD",
+  "features":[
+    [
+      {
+        "name":"Turn or Rebuke Undead"
+      }
+    ]
+  ],
+  "spells":{
+    "group":"DIVINE",
+    "known":[ ],
+    "perDay":[
+      [3, 1],
+      [4, 2],
+      [4, 2, 1],
+      [5, 3, 2],
+      [5, 3, 2, 1],
+      [5, 3, 3, 2],
+      [6, 4, 3, 2, 1],
+      [6, 4, 3, 3, 2],
+      [6, 4, 4, 3, 2, 1],
+      [6, 4, 4, 3, 3, 2],
+      [6, 5, 4, 4, 3, 2, 1],
+      [6, 5, 4, 4, 3, 3, 2],
+      [6, 5, 5, 4, 4, 3, 2, 1],
+      [6, 5, 5, 4, 4, 3, 3, 2],
+      [6, 5, 5, 5, 4, 4, 3, 2, 1],
+      [6, 5, 5, 5, 4, 4, 4, 3, 2],
+      [6, 5, 5, 5, 5, 4, 4, 3, 2, 1],
+      [6, 5, 5, 5, 5, 4, 4, 3, 3, 2],
+      [6, 5, 5, 5, 5, 5, 4, 4, 3, 3],
+      [6, 5, 5, 5, 5, 5, 4, 4, 4, 4]
+    ],
+    "spellList":[
+      ["Create Water"]
+    ]
+  }
+}

+ 15 - 0
resources/spells/default.json

@@ -0,0 +1,15 @@
+{
+  "Create Water":{
+    "school":"Conjuration",
+    "subSchool":"Creation",
+    "keywords":["Water"],
+    "components":["V","S"],
+    "castingTime":"Standard",
+    "range":"Medium",
+    "effect":"Up to 2 gallons of water/level",
+    "duration":"Instantaneous",
+    "savingThrow":"None",
+    "spellResistence":false,
+    "description":"This spell generates wholesome, drinkable water, just like clean rain water. Water can be created in an area as small as will actually contain the liquid, or in an area three times as large—possibly creating a downpour or filling many small receptacles. Note: Conjuration spells can't create substances or objects within a creature. Water weighs about 8 pounds per gallon. One cubic foot of water contains roughly 8 gallons and weighs about 60 pounds."
+  }
+}

+ 17 - 0
src/org/leumasjaffe/charsheet/Test.java

@@ -0,0 +1,17 @@
+package org.leumasjaffe.charsheet;
+
+import org.leumasjaffe.charsheet.view.D20Sheet;
+
+import lombok.SneakyThrows;
+
+public class Test {
+
+	@SneakyThrows
+	public static void main(String[] args) {
+		D20Sheet frame = new D20Sheet();
+		frame.pack();
+		frame.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE);
+		frame.setVisible(true);
+	}
+
+}

+ 51 - 0
src/org/leumasjaffe/charsheet/entity/AbilityScores.java

@@ -0,0 +1,51 @@
+package org.leumasjaffe.charsheet.entity;
+
+import org.leumasjaffe.charsheet.entity.viewable.IntValue;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NonNull;
+import lombok.experimental.FieldDefaults;
+
+@Data
+@AllArgsConstructor
+@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
+public class AbilityScores {
+	@Data
+	@AllArgsConstructor
+	@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
+	public static class Scores {
+		IntValue str; 
+		IntValue dex; 
+		IntValue con; 
+		@JsonProperty(value="int") IntValue Int; 
+		IntValue wis; 
+		IntValue cha;
+		
+		@JsonCreator
+		Scores() {
+			this.str = new IntValue();
+			this.dex = new IntValue();
+			this.con = new IntValue();
+			this.Int = new IntValue();
+			this.wis = new IntValue();
+			this.cha = new IntValue();
+		}
+	}
+	
+	@NonNull Scores base;
+	@NonNull Scores temp;
+	
+	public AbilityScores() {
+		this.base = new Scores();
+		this.temp = new Scores();
+	}
+	
+	public static int modifer(int val) {
+		return val / 2 - 5;
+	}
+}

+ 5 - 0
src/org/leumasjaffe/charsheet/entity/Alignment.java

@@ -0,0 +1,5 @@
+package org.leumasjaffe.charsheet.entity;
+
+public enum Alignment {
+	LG, NG, CG, LN, TN, CN, LE, NE, CE
+}

+ 23 - 0
src/org/leumasjaffe/charsheet/entity/AttackQuality.java

@@ -0,0 +1,23 @@
+package org.leumasjaffe.charsheet.entity;
+
+import lombok.AccessLevel;
+import lombok.RequiredArgsConstructor;
+import lombok.experimental.FieldDefaults;
+
+@RequiredArgsConstructor
+@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
+public enum AttackQuality {
+	GOOD(1, 0),
+	AVERAGE(1, 4),
+	POOR(2, 0);
+	
+	int increaseEvery;
+	int exceptEvery;
+	
+	int getBonus(int level) {
+		if ( exceptEvery > 0 ) {
+			level -= ( ( level - 1 ) / exceptEvery ) + 1;
+		}
+		return level / increaseEvery;
+	}
+}

+ 17 - 0
src/org/leumasjaffe/charsheet/entity/DDActionType.java

@@ -0,0 +1,17 @@
+package org.leumasjaffe.charsheet.entity;
+
+import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
+import lombok.ToString;
+
+@RequiredArgsConstructor
+@ToString
+public enum DDActionType {
+	Standard, Move, FullRound("Full-Round"), Swift, Immediate;
+	
+	private DDActionType() {
+		this.name = this.name();
+	}
+	
+	@NonNull String name;
+}

+ 77 - 0
src/org/leumasjaffe/charsheet/entity/DDCharacter.java

@@ -0,0 +1,77 @@
+package org.leumasjaffe.charsheet.entity;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import org.leumasjaffe.charsheet.entity.viewable.ObjectValue;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+import lombok.AccessLevel;
+import lombok.Data;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.NonNull;
+import lombok.experimental.FieldDefaults;
+
+@NoArgsConstructor
+@Data
+@FieldDefaults(level=AccessLevel.PRIVATE)
+@JsonIgnoreProperties(ignoreUnknown=true)
+public class DDCharacter {
+	@NonNull String name = "";
+	
+	@NonNull String player = "";
+	
+	@NonNull @Getter(AccessLevel.NONE) Set<DDCharacterClass> classes = new HashSet<>();
+	
+	@NonNull String race = "";
+	@NonNull Alignment alignment;
+	@NonNull String deity = "";
+	
+	@NonNull ObjectValue<SizeClass> size = new ObjectValue<>(SizeClass.M);
+	int age = -1;
+	@NonNull Gender gender = Gender.N;
+	@NonNull String height;
+	
+	int weight = -1;
+	@NonNull String eyes = "";
+	@NonNull String hair = "";
+	@NonNull String skin = "";
+	
+	@NonNull HitPoints health = new HitPoints();
+	
+	@NonNull AbilityScores abilities = new AbilityScores();
+
+	public String getClassAndLevelString() {
+		final StringBuilder ss = new StringBuilder();
+		final Iterator<DDCharacterClass> it = classes.iterator();
+		while (it.hasNext()) {
+			ss.append(it.next());
+			if (it.hasNext()) { ss.append(" / "); }
+		}
+		return ss.toString();
+	}
+	
+	public Set<DDCharacterClass> getClasses() {
+		return Collections.unmodifiableSet(classes);
+	}
+
+	public int getBaseAttack() {
+		return classes.stream().mapToInt(c -> c.getBab()).sum();
+	}
+	
+	public int getFortSave() {
+		return classes.stream().mapToInt(c -> c.getFort()).sum();
+	}
+	
+	public int getRefSave() {
+		return classes.stream().mapToInt(c -> c.getRef()).sum();
+	}
+	
+	public int getWillSave() {
+		return classes.stream().mapToInt(c -> c.getWill()).sum();
+	}
+}

+ 67 - 0
src/org/leumasjaffe/charsheet/entity/DDCharacterClass.java

@@ -0,0 +1,67 @@
+package org.leumasjaffe.charsheet.entity;
+
+import java.util.Optional;
+
+import org.leumasjaffe.charsheet.entity.magic.DDSpellbook;
+
+import lombok.AccessLevel;
+import lombok.Data;
+import lombok.Getter;
+import lombok.experimental.Delegate;
+import lombok.experimental.FieldDefaults;
+import lombok.experimental.NonFinal;
+
+@Data
+@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
+public class DDCharacterClass {
+	private static final class Reference {
+		DDClass base;
+		
+		public boolean equals(Object o) {
+			return base.equals(o);
+		}
+
+		public String getName() {
+			return base.getName();
+		}
+
+		public int hashCode() {
+			return base.hashCode();
+		}
+
+		public String toString() {
+			return base.toString();
+		}
+
+		@SuppressWarnings("unused")
+		public Reference(final String name) {
+			this.base = DDClass.getFromResource(name);
+		}
+	}
+
+	@NonFinal int level;
+//	@NonNull List<Integer> healthRolls;
+	@Delegate @Getter(AccessLevel.NONE) Reference name;
+	
+	@Getter(AccessLevel.NONE) Optional<DDSpellbook> spellBook;
+	
+	public String toString() {
+		return getName() + " " + getLevel();
+	}
+
+	public int getBab() {
+		return name.base.getBab().getBonus(level);
+	}
+	
+	public int getFort() {
+		return name.base.getFort().getBonus(level);
+	}
+	
+	public int getRef() {
+		return name.base.getRef().getBonus(level);
+	}
+	
+	public int getWill() {
+		return name.base.getWill().getBonus(level);
+	}
+}

+ 41 - 0
src/org/leumasjaffe/charsheet/entity/DDClass.java

@@ -0,0 +1,41 @@
+package org.leumasjaffe.charsheet.entity;
+
+import java.io.File;
+import java.util.List;
+import java.util.Optional;
+
+import org.leumasjaffe.charsheet.entity.magic.DDSpellList;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
+
+import lombok.AccessLevel;
+import lombok.Data;
+import lombok.Getter;
+import lombok.NonNull;
+import lombok.SneakyThrows;
+import lombok.experimental.FieldDefaults;
+
+@Data
+@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
+@JsonIgnoreProperties(ignoreUnknown=true)
+public class DDClass {
+	@NonNull String name;
+	
+	@NonNull AttackQuality bab;
+	@NonNull SaveQuality fort;
+	@NonNull SaveQuality ref;
+	@NonNull SaveQuality will;
+	
+	@Getter(AccessLevel.NONE) @NonNull List<List<DDFeature>> features;
+	
+	@Getter(AccessLevel.NONE) @NonNull Optional<DDSpellList> spells;
+	
+	@SneakyThrows
+	public static DDClass getFromResource(final String name) {
+		final ObjectMapper mapper = new ObjectMapper();
+		mapper.registerModule(new Jdk8Module());
+		return mapper.readValue(new File("resources/classes/" + name + ".json"), DDClass.class);
+	}
+}

+ 14 - 0
src/org/leumasjaffe/charsheet/entity/DDFeature.java

@@ -0,0 +1,14 @@
+package org.leumasjaffe.charsheet.entity;
+
+import lombok.AccessLevel;
+import lombok.Data;
+import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
+import lombok.experimental.FieldDefaults;
+
+@Data
+@RequiredArgsConstructor
+@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
+public class DDFeature {
+	@NonNull String name;
+}

+ 5 - 0
src/org/leumasjaffe/charsheet/entity/Gender.java

@@ -0,0 +1,5 @@
+package org.leumasjaffe.charsheet.entity;
+
+public enum Gender {
+	M, F, N
+}

+ 27 - 0
src/org/leumasjaffe/charsheet/entity/HitPoints.java

@@ -0,0 +1,27 @@
+package org.leumasjaffe.charsheet.entity;
+
+import org.leumasjaffe.charsheet.entity.viewable.IntValue;
+
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.experimental.FieldDefaults;
+
+@Getter
+@AllArgsConstructor
+@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
+public class HitPoints {
+	IntValue total;
+	IntValue rolled;
+	IntValue current;
+	IntValue temp;
+	IntValue nonlethal;
+	
+	public HitPoints() {
+		this.total = new IntValue();
+		this.rolled = new IntValue();
+		this.current = new IntValue();
+		this.temp = new IntValue();
+		this.nonlethal = new IntValue();
+	}
+}

+ 19 - 0
src/org/leumasjaffe/charsheet/entity/SaveQuality.java

@@ -0,0 +1,19 @@
+package org.leumasjaffe.charsheet.entity;
+
+import lombok.AccessLevel;
+import lombok.RequiredArgsConstructor;
+import lombok.experimental.FieldDefaults;
+
+@RequiredArgsConstructor
+@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
+public enum SaveQuality {
+	GOOD(2, 2), 
+	POOR(0, 3);
+	
+	int atZero;
+	int increaseEvery;
+	
+	int getBonus(int level) {
+		return ( level / increaseEvery ) + atZero;
+	}
+}

+ 22 - 0
src/org/leumasjaffe/charsheet/entity/SizeClass.java

@@ -0,0 +1,22 @@
+package org.leumasjaffe.charsheet.entity;
+
+import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor
+public enum SizeClass {
+	F("Fine"      ,  +8, -16, +16),
+	D("Diminutive",  +4, -12, +12),
+	T("Tiny"      ,  +2,  -8,  +8),
+	S("Small"     ,  +1,  -4,  +4),
+	M("Medium"    ,  +0,  +0,  +0),
+	L("Large"     ,  -1,  +4,  -4),
+	H("Huge"      ,  -2,  +8,  -8),
+	G("Gargantuan",  -4, +12, -12),
+	C("Colossal"  ,  -8, +16, -16);
+	
+	public final @NonNull String name;
+	public final int modifier;
+	public final int specialModifier;
+	public final int hideModifier;
+}

+ 80 - 0
src/org/leumasjaffe/charsheet/entity/magic/Area.java

@@ -0,0 +1,80 @@
+package org.leumasjaffe.charsheet.entity.magic;
+
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
+import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
+
+import lombok.AccessLevel;
+import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
+import lombok.experimental.FieldDefaults;
+
+@JsonTypeInfo(use=Id.CLASS, include=As.PROPERTY, property="class")
+public interface Area {
+	public static enum Emission { BURST, EMANATION, SPREAD, NONE }
+	public static enum Shape { CONE, CYLINDER, LINE, SPHERE }
+
+	@RequiredArgsConstructor
+	@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
+	public static class Line implements Area {
+		int distance;
+
+		public String toString() {
+			return distance + " ft. line from you";
+		}
+	}
+
+	@RequiredArgsConstructor
+	@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
+	public static class Cone implements Area {
+		@NonNull Emission emit;
+
+		public String toString() {
+			return "Cone-shaped " + emit.name().toLowerCase();
+		}
+	}
+
+	@RequiredArgsConstructor
+	@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
+	public static class Sphere implements Area {
+		int radius;
+		@NonNull Emission emit;
+		boolean aroundYou;
+
+		public String toString() {
+			// TODO size/level spheres like Otiluke’s Resilient Sphere PH258
+			final StringBuilder str = new StringBuilder();
+			if ( radius == 0 ) { str.append("5-ft.-diameter "); }
+			else { str.append(radius).append("-ft.-radius "); }
+
+			switch (emit) {
+			case NONE:
+				str.append("sphere");
+				break;
+			case EMANATION:
+				str.append("spherical ");
+			default:
+				str.append(emit);
+				break;
+			}
+			if ( aroundYou ) { str.append(", centered around you");	}
+
+			return str.toString();
+		}
+	}
+
+	@RequiredArgsConstructor
+	@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
+	public static class Cylinder implements Area {
+		int radius;
+		int height;
+		
+		public String toString() {
+			// TODO Grow by level cylinder (e.g. Control Winds PH214)
+			final StringBuilder str = new StringBuilder("Cylinder (");
+			str.append(radius).append("-ft. radius, ");
+			str.append(height).append("-ft. high)");
+			return str.toString();
+		}
+	}
+}

+ 73 - 0
src/org/leumasjaffe/charsheet/entity/magic/DDSpell.java

@@ -0,0 +1,73 @@
+package org.leumasjaffe.charsheet.entity.magic;
+
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Set;
+
+import org.leumasjaffe.charsheet.entity.DDActionType;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+import lombok.AccessLevel;
+import lombok.Data;
+import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
+import lombok.experimental.FieldDefaults;
+
+@Data
+@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
+@JsonIgnoreProperties(ignoreUnknown=true)
+public class DDSpell {
+	public static enum School {
+		Abjuration,
+		Conjuration,
+		Divination,
+		Enchantment,
+		Evocation,
+		Illusion,
+		Necromancy,
+		Transmutation;
+	};
+	
+	public static enum SubSchool {
+		Calling, Creation, Healing, Summoning, Teleportation, // Conjuration
+		Scrying, // Divination
+		Charm, Compulsion, // Enchantment
+		Figment, Glamer, Pattern, Phantasm, Shadow; // Illusion
+	}
+	
+	@RequiredArgsConstructor
+	public static enum Component {
+		V("Verbal"),
+		S("Somatic"),
+		M("Material"), 
+		F("Focus"),
+		DF("Divine Focus"),
+		XP("Experience Point");
+		
+		@NonNull String name;
+		
+		public String toString() {
+			return name;
+		}
+	}
+	
+	@NonNull School school;
+	SubSchool subSchool;
+	@NonNull Set<String> keywords;
+	
+	@NonNull EnumSet<Component> components = EnumSet.noneOf(Component.class);
+	@NonNull DDActionType castingTime;
+	@NonNull Range range;
+	String effect; // TODO
+	Area area;
+	List<String> targets; // TODO
+	@NonNull String duration; // TODO
+	@NonNull String savingThrow; // TODO
+	boolean allowsSpellResistance;
+	@NonNull String description;
+	
+	public String getSpellSchool() {
+		return subSchool == null ? school.toString() : school.toString() + " (" + subSchool.toString() + ")";
+	}
+}

+ 52 - 0
src/org/leumasjaffe/charsheet/entity/magic/DDSpellFactory.java

@@ -0,0 +1,52 @@
+package org.leumasjaffe.charsheet.entity.magic;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
+
+import lombok.AccessLevel;
+import lombok.SneakyThrows;
+import lombok.Synchronized;
+import lombok.experimental.FieldDefaults;
+import lombok.experimental.UtilityClass;
+
+@UtilityClass
+@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
+public final class DDSpellFactory {
+	Set<String> resourcesLoaded = new HashSet<>();
+	Map<String, DDSpell> spellStore = new HashMap<>();
+	
+	ObjectMapper mapper = new ObjectMapper();
+
+	
+	static {
+		mapper.registerModule(new Jdk8Module());
+		loadIfAbsent("resources/spells/default.json");
+	}
+	
+	public DDSpell loadSpellWithResource(final String rname, final String name) {
+		loadIfAbsent(rname);
+		return loadSpell(name);
+	}
+	
+	public DDSpell loadSpell(final String name) {
+		return spellStore.get(name);
+	}
+	
+	@Synchronized
+	@SneakyThrows
+	private void loadIfAbsent(final String rname) {
+		final Map<String, DDSpell> temp = mapper.readValue(
+				new File(rname),
+				new TypeReference<Map<String, DDSpell>>() {
+				});
+		resourcesLoaded.add(rname);
+		spellStore.putAll(temp);
+	}
+}

+ 42 - 0
src/org/leumasjaffe/charsheet/entity/magic/DDSpellList.java

@@ -0,0 +1,42 @@
+package org.leumasjaffe.charsheet.entity.magic;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+import lombok.AccessLevel;
+import lombok.Data;
+import lombok.NonNull;
+import lombok.experimental.FieldDefaults;
+
+@Data
+@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
+@JsonIgnoreProperties(ignoreUnknown=true)
+public class DDSpellList {
+	@NonNull Source group;
+	
+	@NonNull List<List<Integer>> known;
+	@NonNull List<List<Integer>> perDay;
+
+	@NonNull List<SpellList> spellList;
+
+	@Data
+	public static class SpellList {
+		@NonNull List<DDSpell> spellList;
+		
+		@JsonCreator
+		public SpellList(List<String> data) {
+			this.spellList = new ArrayList<>(data.size());
+			int idx = -1;
+			for ( final String name : data ) {
+				if ((idx = name.indexOf(':')) != -1) {
+					spellList.add(DDSpellFactory.loadSpellWithResource(name.substring(0, idx), name.substring(idx))); 
+				} else {
+					spellList.add(DDSpellFactory.loadSpell(name));
+				}
+			}
+		}
+	} 
+}

+ 18 - 0
src/org/leumasjaffe/charsheet/entity/magic/DDSpellbook.java

@@ -0,0 +1,18 @@
+package org.leumasjaffe.charsheet.entity.magic;
+
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+import lombok.AccessLevel;
+import lombok.Data;
+import lombok.NonNull;
+import lombok.experimental.FieldDefaults;
+
+@Data
+@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
+@JsonIgnoreProperties(ignoreUnknown=true)
+public class DDSpellbook {
+	@NonNull List<List<DDSpell>> spellsPrepared;
+	@NonNull List<List<DDSpell>> spellsAtCircle; 
+}

+ 86 - 0
src/org/leumasjaffe/charsheet/entity/magic/Range.java

@@ -0,0 +1,86 @@
+package org.leumasjaffe.charsheet.entity.magic;
+
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+
+import lombok.AccessLevel;
+import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
+import lombok.experimental.FieldDefaults;
+
+public interface Range {
+	public static final Range Personal = new Basic("Personal"), 
+			Touch = new Basic("Touch"), 
+			Close = new WithLevelGrowth("Close", 25, 5, 1), 
+			Medium = new WithLevelGrowth("Medium", 100, 10, 2), 
+			Long = new WithLevelGrowth("Long", 400, 40, 1), 
+			Unlimited = new Basic("Unlimited");
+	
+	@JsonCreator
+	public static Range select(final String name) {
+		switch (name.toLowerCase()) {
+		case "personal": return Personal;
+		case "touch": return Touch;
+		case "close": return Close;
+		case "medium": return Medium;
+		case "long": return Long;
+		case "unlimited": return Unlimited;
+		}
+		throw new IllegalArgumentException("Unrecognized Range constant: " + name);
+	}
+	
+	@JsonCreator
+	public static Range create(final Map<String, Object> data) {
+		final String type = (String) data.get("type");
+		switch (type.toLowerCase()) {
+		case "basic": return new Basic((String) data.get("name"));
+		case "withdistance": return new WithDistance((String) data.get("name"), (Integer) data.get("range"));
+		case "withlevelgrowth": return new WithLevelGrowth((String) data.get("name"), (Integer) data.get("range"), 
+				(Integer) data.get("per"), (Integer) data.get("step"));
+		}
+		throw new IllegalArgumentException("Unrecognized Range type: " + type);
+	}
+	
+	@RequiredArgsConstructor
+	@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
+	public static class Basic implements Range {
+		@NonNull String name;
+		
+		public String toString() {
+			return name;
+		}
+	}
+	
+	@RequiredArgsConstructor
+	@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
+	public static class WithDistance implements Range {
+		@NonNull String name;
+		int range;
+		
+		public String toString() {
+			final StringBuilder str = new StringBuilder(name);
+			str.append(" (").append(range).append(" ft.").append(")");
+			return str.toString();
+		}
+	}
+	
+	@RequiredArgsConstructor
+	@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
+	public static class WithLevelGrowth implements Range {
+		@NonNull String name;
+		int range;
+		int per;
+		int step;
+		
+		public String toString() {
+			final StringBuilder str = new StringBuilder(name);
+			str.append(" (").append(range).append(" ft.");
+			str.append(" + ").append(per).append(" ft./");
+			if ( step == 1 ) { str.append("level"); }
+			else { str.append(step).append(" levels"); }
+			str.append(')');
+			return str.toString();
+		}
+	}
+}

+ 6 - 0
src/org/leumasjaffe/charsheet/entity/magic/Source.java

@@ -0,0 +1,6 @@
+package org.leumasjaffe.charsheet.entity.magic;
+
+public enum Source { 
+	ARCANE, 
+	DIVINE 
+}

+ 30 - 0
src/org/leumasjaffe/charsheet/entity/viewable/IntValue.java

@@ -0,0 +1,30 @@
+package org.leumasjaffe.charsheet.entity.viewable;
+
+import org.leumasjaffe.charsheet.observer.Observable;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+
+@Accessors(fluent=true)
+@Data
+@NoArgsConstructor
+@EqualsAndHashCode(callSuper=false)
+public class IntValue extends Observable {
+	@JsonCreator
+	public IntValue(int v) { this.value = v; }
+	private int value = -1;
+	
+	public String toString() {
+		return Integer.toString(value);
+	}
+	
+	@JsonValue
+	int getSerializable() {
+		return value;
+	}
+}

+ 28 - 0
src/org/leumasjaffe/charsheet/entity/viewable/ObjectValue.java

@@ -0,0 +1,28 @@
+package org.leumasjaffe.charsheet.entity.viewable;
+
+import org.leumasjaffe.charsheet.observer.Observable;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NonNull;
+import lombok.experimental.Accessors;
+
+@Accessors(fluent=true)
+@Data
+@EqualsAndHashCode(callSuper=false)
+public class ObjectValue<T> extends Observable {
+	@JsonCreator public ObjectValue(T v) { this.value = v; }
+	private @NonNull T value;
+	
+	public String toString() {
+		return value.toString();
+	}
+	
+	@JsonValue
+	T getSerializable() {
+		return value;
+	}
+}

+ 30 - 0
src/org/leumasjaffe/charsheet/entity/viewable/StringValue.java

@@ -0,0 +1,30 @@
+package org.leumasjaffe.charsheet.entity.viewable;
+
+import org.leumasjaffe.charsheet.observer.Observable;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import lombok.NonNull;
+import lombok.experimental.Accessors;
+
+@Accessors(fluent=true)
+@NoArgsConstructor
+@Data
+@EqualsAndHashCode(callSuper=false)
+public class StringValue extends Observable {
+	@JsonCreator public StringValue(String v) { this.value = v; }
+	private @NonNull String value = "";
+	
+	public String toString() {
+		return value;
+	}
+	
+	@JsonValue
+	String getSerializable() {
+		return value;
+	}
+}

+ 7 - 0
src/org/leumasjaffe/charsheet/observer/Observable.java

@@ -0,0 +1,7 @@
+package org.leumasjaffe.charsheet.observer;
+
+import java.util.UUID;
+
+public class Observable {
+	public final transient UUID observableId = UUID.randomUUID();
+}

+ 45 - 0
src/org/leumasjaffe/charsheet/observer/ObservableController.java

@@ -0,0 +1,45 @@
+package org.leumasjaffe.charsheet.observer;
+
+import java.util.Objects;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+import javax.swing.text.JTextComponent;
+
+import org.leumasjaffe.charsheet.observer.helper.Helper;
+import org.leumasjaffe.event.AnyActionDocumentListener;
+
+import lombok.experimental.FieldDefaults;
+import lombok.AccessLevel;
+
+@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
+public class ObservableController<T extends Observable> extends ObservableListener<T> {
+	Helper<T> func;
+
+	public ObservableController(final JTextComponent text, final Helper<T> func,
+			final Function<? super T, String> stringify) {
+		super(text, stringify);
+		this.func = func;
+		AnyActionDocumentListener.skipEmpty(text, evt -> accept( ) );
+	}
+	
+	public ObservableController(final JTextComponent text, final Helper<T> func,
+			final Function<? super T, String> stringify, final Consumer<T> onEmpty) {
+		super(text, stringify);
+		this.func = func;
+		AnyActionDocumentListener.emptyOrText( text, 
+				e -> onEmpty.accept( observed ), 
+				evt -> accept( ) );
+	}
+
+	private boolean update() {
+		return func.apply( text.getText( ), observed );
+	}
+
+	private void accept() {
+		Objects.requireNonNull( observed );
+		if ( update( ) ) {
+			ObserverDispatch.notifySubscribers( observed, this );
+		}
+	}
+}

+ 42 - 0
src/org/leumasjaffe/charsheet/observer/ObservableListener.java

@@ -0,0 +1,42 @@
+package org.leumasjaffe.charsheet.observer;
+
+import java.util.Objects;
+import java.util.function.Function;
+
+import javax.swing.text.JTextComponent;
+
+import lombok.experimental.FieldDefaults;
+import lombok.experimental.NonFinal;
+import lombok.AccessLevel;
+import lombok.RequiredArgsConstructor;
+
+@FieldDefaults(level=AccessLevel.PROTECTED, makeFinal=true)
+@RequiredArgsConstructor
+public class ObservableListener<T extends Observable> {
+	JTextComponent text;
+	Function<? super T, String> stringify;
+
+	@NonFinal T observed = null;
+
+	public void setObserved( T obs, Observable... extra ) {
+		Objects.requireNonNull( obs );
+		if ( obs == observed ) return;
+		ObserverDispatch.unsubscribeAll( this );
+		observed = obs;
+		updateComponent( );
+		ObserverDispatch.subscribe( observed, this, () -> updateComponent( ) );
+		for ( int i = 0; i < extra.length; ++i ) {
+			ObserverDispatch.subscribe( extra[i], this, () -> updateComponent( ) );
+		}
+	}
+
+	private void updateComponent() {
+		text.setText( stringify.apply( observed ) );
+	}
+
+	public static class ToString implements Function<Object, String> {
+		public String apply(Object t) {
+			return t.toString();
+		}
+	}
+}

+ 37 - 0
src/org/leumasjaffe/charsheet/observer/ObserverDispatch.java

@@ -0,0 +1,37 @@
+package org.leumasjaffe.charsheet.observer;
+
+import java.util.UUID;
+
+import com.google.common.collect.LinkedListMultimap;
+import com.google.common.collect.Multimap;
+
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.experimental.FieldDefaults;
+import lombok.experimental.UtilityClass;
+
+@UtilityClass
+@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
+public class ObserverDispatch {
+	@AllArgsConstructor
+	@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
+	private static class Pair {
+		Object obj;
+		Subscriber sub;
+	}
+	
+	Multimap<UUID, Pair> observers = LinkedListMultimap.create();
+	
+	public void subscribe(Observable target, Object src, Subscriber sub) {
+		observers.put(target.observableId, new Pair(src, sub));
+	}
+	
+	public void unsubscribeAll(Object src) {
+		observers.entries().removeIf( e -> e.getValue().obj == src );
+	}
+	
+	public void notifySubscribers(Observable target, Object src) {
+		observers.get(target.observableId).stream().filter( p -> p.obj != src ).forEach( 
+				p -> p.sub.notifyUpdate() );
+	}
+}

+ 6 - 0
src/org/leumasjaffe/charsheet/observer/Subscriber.java

@@ -0,0 +1,6 @@
+package org.leumasjaffe.charsheet.observer;
+
+@FunctionalInterface
+public interface Subscriber {
+	public void notifyUpdate();
+}

+ 16 - 0
src/org/leumasjaffe/charsheet/observer/helper/AbilModStringify.java

@@ -0,0 +1,16 @@
+package org.leumasjaffe.charsheet.observer.helper;
+
+import java.util.function.Function;
+
+import org.leumasjaffe.charsheet.entity.AbilityScores;
+import org.leumasjaffe.charsheet.entity.viewable.IntValue;
+import org.leumasjaffe.charsheet.view.StringHelper;
+
+public class AbilModStringify implements Function<IntValue, String> {
+	
+	@Override
+	public String apply(IntValue t) {
+		return StringHelper.toString(AbilityScores.modifer(t.value()));
+	}
+
+}

+ 10 - 0
src/org/leumasjaffe/charsheet/observer/helper/Helper.java

@@ -0,0 +1,10 @@
+package org.leumasjaffe.charsheet.observer.helper;
+
+import java.util.function.BiFunction;
+
+import org.leumasjaffe.charsheet.observer.Observable;
+
+//  @FunctionalInterface
+  public interface Helper<T extends Observable> extends BiFunction<String, T, Boolean> {
+//    public boolean update( final String str, final T ref );
+  }

+ 13 - 0
src/org/leumasjaffe/charsheet/observer/helper/IntValueHelper.java

@@ -0,0 +1,13 @@
+package org.leumasjaffe.charsheet.observer.helper;
+
+import org.leumasjaffe.charsheet.entity.viewable.IntValue;
+
+public class IntValueHelper implements Helper<IntValue> {
+    public Boolean apply( final String str, final IntValue ref ) {
+      if ( ! Character.isDigit(str.charAt(0)) ) return false;
+      final int newValue = Integer.parseInt( str );
+      if ( newValue == ref.value( ) ) return false;
+      ref.value( newValue );
+      return true;
+    }
+  }

+ 21 - 0
src/org/leumasjaffe/charsheet/observer/helper/IntValueStringify.java

@@ -0,0 +1,21 @@
+package org.leumasjaffe.charsheet.observer.helper;
+
+import java.util.function.Function;
+
+import org.leumasjaffe.charsheet.entity.viewable.IntValue;
+import org.leumasjaffe.charsheet.view.StringHelper;
+
+import lombok.experimental.UtilityClass;
+
+@UtilityClass
+public class IntValueStringify {
+	
+	public Function<IntValue, String> instance() {
+		return (v) -> StringHelper.toString(v.value());
+	}
+	
+	public Function<IntValue, String> withDefault(final int def) {
+		return (v) -> StringHelper.toString(v.value(), def);
+	}
+
+}

+ 11 - 0
src/org/leumasjaffe/charsheet/observer/helper/StringValueHelper.java

@@ -0,0 +1,11 @@
+package org.leumasjaffe.charsheet.observer.helper;
+
+import org.leumasjaffe.charsheet.entity.viewable.StringValue;
+
+public class StringValueHelper implements Helper<StringValue> {
+    public Boolean apply( final String str, final StringValue ref ) {
+      if ( str.equals( ref.value( ) ) ) return false;
+      ref.value( str );
+      return true;
+    }
+  }

+ 9 - 0
src/org/leumasjaffe/charsheet/observer/helper/Stringify.java

@@ -0,0 +1,9 @@
+package org.leumasjaffe.charsheet.observer.helper;
+
+import java.util.function.Function;
+
+public class Stringify implements Function<Object, String> {
+	public String apply(Object t) {
+		return t.toString();
+	}
+  }

+ 66 - 0
src/org/leumasjaffe/charsheet/view/ClassTab.java

@@ -0,0 +1,66 @@
+package org.leumasjaffe.charsheet.view;
+
+import javax.swing.JPanel;
+
+import lombok.AccessLevel;
+import lombok.experimental.FieldDefaults;
+import java.awt.GridBagLayout;
+import javax.swing.JTextField;
+
+import org.leumasjaffe.charsheet.entity.DDCharacterClass;
+
+import java.awt.GridBagConstraints;
+import java.awt.Insets;
+
+@FieldDefaults(level=AccessLevel.PRIVATE)
+public class ClassTab extends JPanel {
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = 1L;
+
+	DDCharacterClass model;
+	
+	JTextField name;
+	JTextField level;
+	
+	public ClassTab(DDCharacterClass cc) {
+		this.model = cc;
+		
+		GridBagLayout gridBagLayout = new GridBagLayout();
+		gridBagLayout.columnWidths = new int[]{0, 0, 0};
+		gridBagLayout.rowHeights = new int[]{0, 0};
+		gridBagLayout.columnWeights = new double[]{0.0, 1.0, Double.MIN_VALUE};
+		gridBagLayout.rowWeights = new double[]{0.0, Double.MIN_VALUE};
+		setLayout(gridBagLayout);
+		
+		name = new JTextField();
+		name.setEditable(false);
+		GridBagConstraints gbc_name = new GridBagConstraints();
+		gbc_name.insets = new Insets(0, 0, 0, 5);
+		gbc_name.fill = GridBagConstraints.HORIZONTAL;
+		gbc_name.gridx = 0;
+		gbc_name.gridy = 0;
+		add(name, gbc_name);
+		name.setColumns(10);
+		
+		level = new JTextField();
+		level.setEditable(false);
+		GridBagConstraints gbc_level = new GridBagConstraints();
+		gbc_level.fill = GridBagConstraints.HORIZONTAL;
+		gbc_level.gridx = 1;
+		gbc_level.gridy = 0;
+		add(level, gbc_level);
+		level.setColumns(10);
+	}
+	
+	@Override
+	public String getName() {
+		return this.model.getName();
+	}
+	
+	public void updateModel() {
+		name.setText(this.model.getName());
+		level.setText(StringHelper.toString(this.model.getLevel()));
+	}
+}

+ 154 - 0
src/org/leumasjaffe/charsheet/view/D20Sheet.java

@@ -0,0 +1,154 @@
+package org.leumasjaffe.charsheet.view;
+
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JTabbedPane;
+
+import org.leumasjaffe.charsheet.entity.DDCharacter;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
+
+import lombok.AccessLevel;
+import lombok.SneakyThrows;
+import lombok.experimental.FieldDefaults;
+import lombok.experimental.NonFinal;
+
+import java.awt.BorderLayout;
+import javax.swing.JPanel;
+import javax.swing.JMenuBar;
+import javax.swing.JMenu;
+import javax.swing.JMenuItem;
+import javax.swing.KeyStroke;
+import java.awt.event.KeyEvent;
+import java.awt.event.WindowEvent;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.awt.event.InputEvent;
+
+@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
+public class D20Sheet extends JFrame {
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = 1L;
+	ObjectMapper mapper = new ObjectMapper();
+	@NonFinal File currentlyOpenFile = null;
+	@NonFinal DDCharacter model = new DDCharacter();
+	@NonFinal boolean isDirty = false;
+
+	JTabbedPane tabbedPane;
+	SummaryTab summaryTab;
+	List<ClassTab> classTabs = new ArrayList<>();
+	JPanel abilitiesTab;
+	JPanel skillTab;
+	JPanel equipmentTab;
+	
+	public D20Sheet() {
+		// Set up local state variables
+		final JFileChooser fc = new JFileChooser("resources");
+		mapper.registerModule(new Jdk8Module());
+
+		// Set up GUI Data (WindowBuilder)
+		tabbedPane = new JTabbedPane(JTabbedPane.TOP);
+		getContentPane().add(tabbedPane, BorderLayout.CENTER);
+		
+		summaryTab = new SummaryTab();
+		abilitiesTab = new JPanel();
+		skillTab = new JPanel();
+		equipmentTab = new JPanel();
+		
+		JMenuBar menuBar = new JMenuBar();
+		setJMenuBar(menuBar);
+		
+		JMenu mnFile = new JMenu("File");
+		menuBar.add(mnFile);
+		
+		JMenuItem mntmNew = new JMenuItem("New");
+		mntmNew.addActionListener(e -> { setModel(new DDCharacter()); });
+		mntmNew.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, InputEvent.CTRL_MASK));
+		mnFile.add(mntmNew);
+		
+		JMenuItem mntmOpen = new JMenuItem("Open");
+		mntmOpen.addActionListener(e -> { 
+			int rv = fc.showOpenDialog(this);
+			if ( rv == JFileChooser.APPROVE_OPTION ) {
+				loadModelResource( fc.getSelectedFile() );
+			}
+		});
+		mntmOpen.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, InputEvent.CTRL_MASK));
+		mnFile.add(mntmOpen);
+		
+		JMenuItem mntmSave = new JMenuItem("Save");
+		mntmSave.addActionListener( e -> {
+			if ( currentlyOpenFile == null ) { saveAs(fc); } 
+			else { saveModelResource(); }
+		} );
+		mntmSave.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_MASK));
+		mnFile.add(mntmSave);
+		
+		JMenuItem mntmSaveAs = new JMenuItem("Save As");
+		mntmSaveAs.addActionListener( e -> saveAs(fc) );
+		mntmSaveAs.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_MASK | InputEvent.SHIFT_MASK));
+		mnFile.add(mntmSaveAs);
+				
+		JMenuItem mntmExit = new JMenuItem("Exit");
+		mntmExit.addActionListener( e -> { this.dispatchEvent(new WindowEvent(this, WindowEvent.WINDOW_CLOSING)); } );
+		mntmExit.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q, InputEvent.CTRL_MASK));
+		mnFile.add(mntmExit);
+
+		// Set up post-GUI dependencies
+		setModel(model);
+		updateModel();
+	}
+
+	private void reorderTabs() {
+		this.tabbedPane.removeAll();
+		tabbedPane.addTab("Summary", null, summaryTab, null);
+		classTabs.forEach( t -> tabbedPane.addTab(t.getName(), null, t, null) );
+		tabbedPane.addTab("Abilities", null, abilitiesTab, "Racial and Class Abilities, Feats, etc.");
+		tabbedPane.addTab("Skills", null, skillTab, null);
+		tabbedPane.addTab("Gear & Items", null, equipmentTab, null);
+	}
+
+	private void saveAs(final JFileChooser fc) {
+		if ( ! isDirty ) { return; }
+		int rv = fc.showSaveDialog(this);
+		if ( rv == JFileChooser.APPROVE_OPTION ) {
+			currentlyOpenFile = fc.getSelectedFile();
+			saveModelResource();
+		}
+	}
+
+	@SneakyThrows
+	private void loadModelResource(File selectedFile) {
+		currentlyOpenFile = selectedFile;
+		setModel(mapper.readValue(selectedFile, DDCharacter.class));
+		isDirty = false;
+	}
+	
+	@SneakyThrows
+	private void saveModelResource() {
+		if ( currentlyOpenFile != null && isDirty ) {
+			mapper.writeValue(currentlyOpenFile, model);
+			isDirty = false;
+		}
+	}
+	
+	public void setModel(DDCharacter model) {
+		this.model = model;
+		this.summaryTab.setModel(model);
+		updateModel();
+	}
+	
+	
+	public void updateModel() {
+		classTabs.clear();
+		this.model.getClasses().stream().forEach(cc -> classTabs.add(new ClassTab(cc)));
+		
+		reorderTabs();
+		this.summaryTab.updateModel();
+		this.classTabs.forEach(t -> t.updateModel());
+	}
+}

+ 25 - 0
src/org/leumasjaffe/charsheet/view/StringHelper.java

@@ -0,0 +1,25 @@
+package org.leumasjaffe.charsheet.view;
+
+import lombok.experimental.UtilityClass;
+
+@UtilityClass
+public class StringHelper {
+	public String toString(Object o) {
+		if ( o == null ) { return ""; }
+		else { return o.toString(); }
+	}
+	
+	public String toString(int i) {
+		return Integer.toString(i);
+	}
+	
+	public String toString(int i, int ignore) {
+		if ( i == ignore ) { return ""; }
+		else { return Integer.toString(i); }
+	}
+	
+	public String toSignedString(int i) {
+		if ( i > 0 ) { return "+" + i; }
+		else { return toString(i); }
+	}
+}

+ 189 - 0
src/org/leumasjaffe/charsheet/view/SummaryTab.java

@@ -0,0 +1,189 @@
+package org.leumasjaffe.charsheet.view;
+
+import javax.swing.JPanel;
+
+import org.leumasjaffe.charsheet.entity.DDCharacter;
+import org.leumasjaffe.charsheet.view.summary.AbilityPanel;
+import org.leumasjaffe.charsheet.view.summary.ArmorLine;
+import org.leumasjaffe.charsheet.view.summary.AttackLine;
+import org.leumasjaffe.charsheet.view.summary.DamageReductionLine;
+import org.leumasjaffe.charsheet.view.summary.DescriptionPanel;
+import org.leumasjaffe.charsheet.view.summary.HealthLine;
+import org.leumasjaffe.charsheet.view.summary.InitiativeLine;
+import org.leumasjaffe.charsheet.view.summary.ResistancePanel;
+import org.leumasjaffe.charsheet.view.summary.SpellResistanceLine;
+
+import lombok.AccessLevel;
+import lombok.experimental.FieldDefaults;
+
+import java.awt.GridBagLayout;
+import java.awt.GridBagConstraints;
+import java.awt.Insets;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Component;
+import javax.swing.Box;
+
+@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
+public class SummaryTab extends JPanel {
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = 1L;
+	
+	HealthLine healthLine;
+	ArmorLine armorLine;
+	ResistancePanel resistancePanel;
+	AbilityPanel abilityPanel;
+	DescriptionPanel descriptionPanel;
+	InitiativeLine initiativeLine;
+	SpellResistanceLine spellResistLine;
+	DamageReductionLine damageReductionLine;
+	AttackLine attackLine;
+	
+	
+	public SummaryTab() {
+		setBackground(Color.WHITE);
+		GridBagLayout gridBagLayout = new GridBagLayout();
+		gridBagLayout.columnWidths = new int[]{0, 0, 0};
+		gridBagLayout.rowHeights = new int[]{0, 0, 0, 0};
+		gridBagLayout.columnWeights = new double[]{0.0, 0.0, Double.MIN_VALUE};
+		gridBagLayout.rowWeights = new double[]{0.0, 0.0, 0.0, Double.MIN_VALUE};
+		setLayout(gridBagLayout);
+		
+		descriptionPanel = new DescriptionPanel();
+		GridBagConstraints gbc_descriptionPanel = new GridBagConstraints();
+		gbc_descriptionPanel.gridwidth = 2;
+		gbc_descriptionPanel.insets = new Insets(0, 0, 5, 0);
+		gbc_descriptionPanel.fill = GridBagConstraints.BOTH;
+		gbc_descriptionPanel.gridx = 0;
+		gbc_descriptionPanel.gridy = 0;
+		add(descriptionPanel, gbc_descriptionPanel);
+		
+		abilityPanel = new AbilityPanel();
+		GridBagConstraints gbc_abilityPanel = new GridBagConstraints();
+		gbc_abilityPanel.insets = new Insets(0, 0, 5, 5);
+		gbc_abilityPanel.fill = GridBagConstraints.BOTH;
+		gbc_abilityPanel.gridx = 0;
+		gbc_abilityPanel.gridy = 1;
+		add(abilityPanel, gbc_abilityPanel);
+		
+		JPanel panel = new JPanel();
+		panel.setOpaque(false);
+		GridBagConstraints gbc_panel = new GridBagConstraints();
+		gbc_panel.insets = new Insets(0, 0, 5, 0);
+		gbc_panel.fill = GridBagConstraints.BOTH;
+		gbc_panel.gridx = 1;
+		gbc_panel.gridy = 1;
+		add(panel, gbc_panel);
+		GridBagLayout gbl_panel = new GridBagLayout();
+		gbl_panel.columnWidths = new int[]{0, 0};
+		gbl_panel.rowHeights = new int[]{0, 0, 0, 0};
+		gbl_panel.columnWeights = new double[]{1.0, Double.MIN_VALUE};
+		gbl_panel.rowWeights = new double[]{0.0, 0.0, 0.0, Double.MIN_VALUE};
+		panel.setLayout(gbl_panel);
+		
+		healthLine = new HealthLine();
+		GridBagConstraints gbc_healthLine = new GridBagConstraints();
+		gbc_healthLine.insets = new Insets(0, 0, 5, 0);
+		gbc_healthLine.fill = GridBagConstraints.BOTH;
+		gbc_healthLine.gridx = 0;
+		gbc_healthLine.gridy = 0;
+		panel.add(healthLine, gbc_healthLine);
+		
+		armorLine = new ArmorLine();
+		GridBagConstraints gbc_armorLine = new GridBagConstraints();
+		gbc_armorLine.insets = new Insets(0, 0, 5, 0);
+		gbc_armorLine.fill = GridBagConstraints.BOTH;
+		gbc_armorLine.gridx = 0;
+		gbc_armorLine.gridy = 1;
+		panel.add(armorLine, gbc_armorLine);
+		
+		resistancePanel = new ResistancePanel();
+		GridBagConstraints gbc_resistancePanel = new GridBagConstraints();
+		gbc_resistancePanel.fill = GridBagConstraints.BOTH;
+		gbc_resistancePanel.gridx = 0;
+		gbc_resistancePanel.gridy = 2;
+		panel.add(resistancePanel, gbc_resistancePanel);
+		
+		JPanel panel_1 = new JPanel();
+		panel_1.setOpaque(false);
+		GridBagConstraints gbc_panel_1 = new GridBagConstraints();
+		gbc_panel_1.gridwidth = 2;
+		gbc_panel_1.insets = new Insets(0, 0, 0, 5);
+		gbc_panel_1.fill = GridBagConstraints.BOTH;
+		gbc_panel_1.gridx = 0;
+		gbc_panel_1.gridy = 2;
+		add(panel_1, gbc_panel_1);
+		GridBagLayout gbl_panel_1 = new GridBagLayout();
+		gbl_panel_1.columnWidths = new int[]{0, 0, 0, 0, 0, 0};
+		gbl_panel_1.rowHeights = new int[]{0, 0, 0};
+		gbl_panel_1.columnWeights = new double[]{0.0, 0.0, 0.0, 0.0, 0.0, Double.MIN_VALUE};
+		gbl_panel_1.rowWeights = new double[]{1.0, 0.0, Double.MIN_VALUE};
+		panel_1.setLayout(gbl_panel_1);
+		
+		initiativeLine = new InitiativeLine();
+		GridBagConstraints gbc_initiativeLine = new GridBagConstraints();
+		gbc_initiativeLine.insets = new Insets(0, 0, 5, 5);
+		gbc_initiativeLine.fill = GridBagConstraints.BOTH;
+		gbc_initiativeLine.gridx = 0;
+		gbc_initiativeLine.gridy = 0;
+		panel_1.add(initiativeLine, gbc_initiativeLine);
+		
+		Component horizontalStrut = Box.createHorizontalStrut(20);
+		horizontalStrut.setPreferredSize(new Dimension(40, 0));
+		GridBagConstraints gbc_horizontalStrut = new GridBagConstraints();
+		gbc_horizontalStrut.insets = new Insets(0, 0, 5, 5);
+		gbc_horizontalStrut.gridx = 1;
+		gbc_horizontalStrut.gridy = 0;
+		panel_1.add(horizontalStrut, gbc_horizontalStrut);
+		
+		spellResistLine = new SpellResistanceLine();
+		GridBagConstraints gbc_spellResistLine = new GridBagConstraints();
+		gbc_spellResistLine.insets = new Insets(0, 0, 5, 5);
+		gbc_spellResistLine.fill = GridBagConstraints.BOTH;
+		gbc_spellResistLine.gridx = 2;
+		gbc_spellResistLine.gridy = 0;
+		panel_1.add(spellResistLine, gbc_spellResistLine);
+		
+		Component horizontalStrut_1 = Box.createHorizontalStrut(20);
+		horizontalStrut_1.setPreferredSize(new Dimension(40, 0));
+		GridBagConstraints gbc_horizontalStrut_1 = new GridBagConstraints();
+		gbc_horizontalStrut_1.insets = new Insets(0, 0, 5, 5);
+		gbc_horizontalStrut_1.gridx = 3;
+		gbc_horizontalStrut_1.gridy = 0;
+		panel_1.add(horizontalStrut_1, gbc_horizontalStrut_1);
+		
+		damageReductionLine = new DamageReductionLine();
+		GridBagConstraints gbc_damageReductionLine = new GridBagConstraints();
+		gbc_damageReductionLine.insets = new Insets(0, 0, 5, 0);
+		gbc_damageReductionLine.fill = GridBagConstraints.BOTH;
+		gbc_damageReductionLine.gridx = 4;
+		gbc_damageReductionLine.gridy = 0;
+		panel_1.add(damageReductionLine, gbc_damageReductionLine);
+		
+		attackLine = new AttackLine();
+		GridBagConstraints gbc_attackLine = new GridBagConstraints();
+		gbc_attackLine.gridwidth = 5;
+		gbc_attackLine.fill = GridBagConstraints.BOTH;
+		gbc_attackLine.gridx = 0;
+		gbc_attackLine.gridy = 1;
+		panel_1.add(attackLine, gbc_attackLine);
+	}
+
+	public void setModel(DDCharacter model) {
+		this.descriptionPanel.setModel(model);
+		this.abilityPanel.setModel(model.getAbilities());
+		this.initiativeLine.setModel(model);
+		this.attackLine.setModel(model);
+		this.resistancePanel.setModel(model);
+		this.healthLine.setModel(model.getHealth());
+	}
+	
+	public void updateModel() {
+		this.descriptionPanel.updateModel();
+		this.attackLine.updateModel();
+		this.resistancePanel.updateModel();
+	}
+
+}

+ 98 - 0
src/org/leumasjaffe/charsheet/view/summary/AbilityBox.java

@@ -0,0 +1,98 @@
+package org.leumasjaffe.charsheet.view.summary;
+
+import static org.leumasjaffe.charsheet.entity.AbilityScores.modifer;
+
+import javax.swing.JFormattedTextField;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+import java.awt.Dimension;
+import java.awt.Color;
+import java.awt.GridBagLayout;
+import java.awt.GridBagConstraints;
+import java.awt.Insets;
+
+import javax.swing.border.LineBorder;
+
+import org.leumasjaffe.charsheet.entity.viewable.IntValue;
+import org.leumasjaffe.charsheet.observer.ObservableController;
+import org.leumasjaffe.charsheet.observer.ObservableListener;
+import org.leumasjaffe.charsheet.observer.helper.IntValueHelper;
+import org.leumasjaffe.charsheet.observer.helper.IntValueStringify;
+import org.leumasjaffe.charsheet.view.StringHelper;
+import org.leumasjaffe.graphics.NumberTextField;
+
+import lombok.AccessLevel;
+import lombok.experimental.FieldDefaults;
+
+import javax.swing.SwingConstants;
+
+@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
+public class AbilityBox extends JPanel {
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = 1L;
+	ObservableListener<IntValue> valueListener;
+	ObservableListener<IntValue> modListener;
+	
+	public AbilityBox(final String name, final String heading) {
+
+		setOpaque(false);
+		setMaximumSize(new Dimension(80, 26));
+		setMinimumSize(new Dimension(80, 26));
+		setPreferredSize(new Dimension(80, 26));
+		GridBagLayout gridBagLayout = new GridBagLayout();
+		gridBagLayout.columnWidths = new int[]{0, 0, 0};
+		gridBagLayout.rowHeights = new int[]{25, 0};
+		gridBagLayout.columnWeights = new double[]{1.0, 1.0, Double.MIN_VALUE};
+		gridBagLayout.rowWeights = new double[]{0.0, Double.MIN_VALUE};
+		setLayout(gridBagLayout);
+	    
+		JFormattedTextField value = new NumberTextField();
+		value.setToolTipText(heading + " Score (" + name + ")");
+		value.setHorizontalAlignment(SwingConstants.CENTER);
+		value.setMinimumSize(new Dimension(30, 20));
+		value.setMaximumSize(new Dimension(30, 20));
+		value.setPreferredSize(new Dimension(30, 20));
+		value.setBorder(new LineBorder(Color.BLACK));
+		GridBagConstraints gbc_value = new GridBagConstraints();
+		gbc_value.insets = new Insets(0, 5, 0, 5);
+		gbc_value.fill = GridBagConstraints.HORIZONTAL;
+		gbc_value.gridx = 0;
+		gbc_value.gridy = 0;
+		add(value, gbc_value);
+		value.setColumns(3);
+		
+		JTextField modifier = new JTextField();
+		modifier.setToolTipText(heading + " Modifier (" + name + ")");
+		modifier.setHorizontalAlignment(SwingConstants.CENTER);
+		modifier.setEditable(false);
+		modifier.setMinimumSize(new Dimension(30, 20));
+		modifier.setMaximumSize(new Dimension(30, 20));
+		modifier.setPreferredSize(new Dimension(30, 20));
+		modifier.setBorder(new LineBorder(Color.BLACK));
+		GridBagConstraints gbc_modifier = new GridBagConstraints();
+		gbc_modifier.insets = new Insets(0, 0, 0, 5);
+		gbc_modifier.fill = GridBagConstraints.HORIZONTAL;
+		gbc_modifier.gridx = 1;
+		gbc_modifier.gridy = 0;
+		add(modifier, gbc_modifier);
+		modifier.setColumns(3);
+
+		valueListener = new ObservableController<>(value, 
+				new IntValueHelper(), IntValueStringify.withDefault(-1),
+				(v) -> {
+					v.value(-1);
+					modifier.setText("");
+				});
+		modListener = new ObservableListener<>(modifier,
+				(v) -> v.value() <= 0 ? "" : 
+					StringHelper.toString(modifer(v.value())));
+	}
+
+	public void setModel(IntValue scores) {
+		valueListener.setObserved(scores);
+		modListener.setObserved(scores);
+	}
+
+}

+ 82 - 0
src/org/leumasjaffe/charsheet/view/summary/AbilityLine.java

@@ -0,0 +1,82 @@
+package org.leumasjaffe.charsheet.view.summary;
+
+import javax.swing.JPanel;
+import javax.swing.JLabel;
+import java.awt.Color;
+import java.awt.Font;
+import javax.swing.border.LineBorder;
+
+import org.leumasjaffe.charsheet.entity.AbilityScores;
+import org.leumasjaffe.charsheet.entity.viewable.IntValue;
+
+import lombok.AccessLevel;
+import lombok.experimental.FieldDefaults;
+
+import java.awt.GridBagLayout;
+import java.util.function.Function;
+import java.awt.GridBagConstraints;
+import java.awt.Dimension;
+import javax.swing.SwingConstants;
+
+@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
+public class AbilityLine extends JPanel {
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = 1L;
+	Function<AbilityScores.Scores, IntValue> access;
+	AbilityBox ability;
+	AbilityBox temporary;
+	
+	public AbilityLine(final String name, Function<AbilityScores.Scores, IntValue> func) {
+		this.access = func;
+		
+		setMaximumSize(new Dimension(220, 26));
+		setMinimumSize(new Dimension(220, 26));
+		setPreferredSize(new Dimension(220, 26));
+		GridBagLayout gridBagLayout = new GridBagLayout();
+		gridBagLayout.columnWidths = new int[]{50, 70, 70, 0};
+		gridBagLayout.rowHeights = new int[]{26, 0};
+		gridBagLayout.columnWeights = new double[]{0.0, 0.0, 0.0, Double.MIN_VALUE};
+		gridBagLayout.rowWeights = new double[]{0.0, Double.MIN_VALUE};
+		setLayout(gridBagLayout);
+		
+		JLabel title = new JLabel(name.toUpperCase());
+		title.setHorizontalAlignment(SwingConstants.CENTER);
+		title.setForeground(Color.WHITE);
+		title.setOpaque(true);
+		title.setMaximumSize(new Dimension(50, 25));
+		title.setMinimumSize(new Dimension(50, 25));
+		title.setPreferredSize(new Dimension(50, 25));
+		title.setBorder(new LineBorder(Color.WHITE));
+		title.setFont(new Font("Tahoma", Font.BOLD, 18));
+		title.setBackground(Color.BLACK);
+		GridBagConstraints gbc_title = new GridBagConstraints();
+		gbc_title.fill = GridBagConstraints.HORIZONTAL;
+		gbc_title.gridx = 0;
+		gbc_title.gridy = 0;
+		add(title, gbc_title);
+		
+		ability = new AbilityBox(name, "Ability");
+		GridBagConstraints gbc_ability = new GridBagConstraints();
+		gbc_ability.fill = GridBagConstraints.HORIZONTAL;
+		gbc_ability.gridx = 1;
+		gbc_ability.gridy = 0;
+		add(ability, gbc_ability);
+		
+		temporary = new AbilityBox(name, "Temporary");
+		temporary.setOpaque(true);
+		temporary.setBackground(Color.LIGHT_GRAY);
+		GridBagConstraints gbc_temporary = new GridBagConstraints();
+		gbc_temporary.fill = GridBagConstraints.HORIZONTAL;
+		gbc_temporary.gridx = 2;
+		gbc_temporary.gridy = 0;
+		add(temporary, gbc_temporary);
+	}
+
+	public void setModel(AbilityScores model) {
+		this.ability.setModel(access.apply(model.getBase()));
+		this.temporary.setModel(access.apply(model.getTemp()));
+	}
+
+}

+ 70 - 0
src/org/leumasjaffe/charsheet/view/summary/AbilityPanel.java

@@ -0,0 +1,70 @@
+package org.leumasjaffe.charsheet.view.summary;
+
+import javax.swing.JPanel;
+import java.awt.GridBagLayout;
+import java.awt.GridBagConstraints;
+import java.awt.Insets;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Function;
+import java.awt.Dimension;
+import java.awt.Color;
+
+import org.leumasjaffe.charsheet.entity.AbilityScores;
+import org.leumasjaffe.charsheet.entity.viewable.IntValue;
+
+import lombok.AccessLevel;
+import lombok.experimental.FieldDefaults;
+
+@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
+public class AbilityPanel extends JPanel {
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = 1L;
+	private static final String[] abils = new String[] { "STR", "DEX", "CON", "INT", "WIS", "CHA" };
+	private static final List<Function<AbilityScores.Scores, IntValue>> funcs = Arrays.asList( 
+		AbilityScores.Scores::getStr, AbilityScores.Scores::getDex, AbilityScores.Scores::getCon,
+		AbilityScores.Scores::getInt, AbilityScores.Scores::getWis, AbilityScores.Scores::getCha
+		);
+	AbilityLine[] lines = new AbilityLine[6];
+	
+	public AbilityPanel() {
+		setMaximumSize(new Dimension(210, 150));
+		setMinimumSize(new Dimension(210, 150));
+		setPreferredSize(new Dimension(210, 150));
+		setBackground(Color.WHITE);
+		GridBagLayout gridBagLayout = new GridBagLayout();
+		gridBagLayout.columnWidths = new int[]{0, 0};
+		gridBagLayout.rowHeights = new int[]{0, 0, 0, 0, 0, 0, 0};
+		gridBagLayout.columnWeights = new double[]{1.0, Double.MIN_VALUE};
+		gridBagLayout.rowWeights = new double[]{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, Double.MIN_VALUE};
+		setLayout(gridBagLayout);
+		
+		for (int i = 0; i < abils.length; ++i) {
+			lines[i] = addAbility(i, abils[i], funcs.get(i));
+		}
+	}
+
+	private AbilityLine addAbility(int y, String name, Function<AbilityScores.Scores, IntValue> func) {
+		AbilityLine abilityLine = new AbilityLine(name, func);
+		abilityLine.setOpaque(false);
+		GridBagConstraints gbc_abilityLine = new GridBagConstraints();
+		gbc_abilityLine.insets = new Insets(0, 0, 0, 5);
+		gbc_abilityLine.fill = GridBagConstraints.BOTH;
+		gbc_abilityLine.gridx = 0;
+		gbc_abilityLine.gridy = y;
+		add(abilityLine, gbc_abilityLine);
+		return abilityLine;
+	}
+
+	public void setModel(AbilityScores model) {
+		for (int i = 0; i < abils.length; ++i) {
+			lines[i].setModel(model);
+		}
+	}
+	
+	public AbilityLine getLine(int i) {
+		return lines[i];
+	}
+}

+ 349 - 0
src/org/leumasjaffe/charsheet/view/summary/ArmorLine.java

@@ -0,0 +1,349 @@
+package org.leumasjaffe.charsheet.view.summary;
+
+import javax.swing.JPanel;
+import java.awt.GridBagLayout;
+import javax.swing.JLabel;
+import java.awt.GridBagConstraints;
+import java.awt.Color;
+import javax.swing.border.LineBorder;
+import java.awt.Font;
+import javax.swing.SwingConstants;
+import java.awt.Dimension;
+import javax.swing.JTextField;
+import java.awt.Insets;
+
+public class ArmorLine extends JPanel {
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = 1L;
+	private JTextField total;
+	private JTextField deflection;
+	private JTextField armor;
+	private JTextField shield;
+	private JTextField dexterity;
+	private JTextField size;
+	private JTextField natural;
+	private JTextField misc;
+	private JTextField touch;
+	private JTextField flatFoot;
+
+	public ArmorLine() {
+		setMinimumSize(new Dimension(400, 46));
+		setMaximumSize(new Dimension(400, 46));
+		setPreferredSize(new Dimension(400, 46));
+		setOpaque(false);
+		GridBagLayout gridBagLayout = new GridBagLayout();
+		gridBagLayout.columnWidths = new int[]{54, 0};
+		gridBagLayout.rowHeights = new int[]{25, 0, 0};
+		gridBagLayout.columnWeights = new double[]{0.0, Double.MIN_VALUE};
+		gridBagLayout.rowWeights = new double[]{0.0, 0.0, Double.MIN_VALUE};
+		setLayout(gridBagLayout);
+		JPanel panel = new JPanel();
+		panel.setOpaque(false);
+		GridBagConstraints gbc_panel = new GridBagConstraints();
+		gbc_panel.insets = new Insets(0, 0, 5, 0);
+		gbc_panel.anchor = GridBagConstraints.NORTHWEST;
+		gbc_panel.gridx = 1;
+		gbc_panel.gridy = 0;
+		add(panel);
+		
+		GridBagLayout gridBagLayout_1 = new GridBagLayout();
+		gridBagLayout_1.columnWidths = new int[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+		gridBagLayout_1.rowHeights = new int[]{0, 0};
+		gridBagLayout_1.columnWeights = new double[]{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, Double.MIN_VALUE};
+		gridBagLayout_1.rowWeights = new double[]{0.0, Double.MIN_VALUE};
+		panel.setLayout(gridBagLayout_1);
+		
+		JLabel lblAc = new JLabel("AC");
+		lblAc.setToolTipText("Armor Class");
+		lblAc.setPreferredSize(new Dimension(50, 25));
+		lblAc.setOpaque(true);
+		lblAc.setMinimumSize(new Dimension(50, 25));
+		lblAc.setMaximumSize(new Dimension(50, 25));
+		lblAc.setHorizontalAlignment(SwingConstants.CENTER);
+		lblAc.setForeground(Color.WHITE);
+		lblAc.setFont(new Font("Tahoma", Font.BOLD, 18));
+		lblAc.setBorder(new LineBorder(Color.WHITE));
+		lblAc.setBackground(Color.BLACK);
+		GridBagConstraints gbc_lblAc = new GridBagConstraints();
+		gbc_lblAc.insets = new Insets(0, 0, 0, 5);
+		gbc_lblAc.anchor = GridBagConstraints.EAST;
+		gbc_lblAc.gridx = 0;
+		gbc_lblAc.gridy = 0;
+		panel.add(lblAc, gbc_lblAc);
+		
+		total = new JTextField();
+		total.setToolTipText("Total AC");
+		total.setPreferredSize(new Dimension(30, 20));
+		total.setMinimumSize(new Dimension(30, 20));
+		total.setMaximumSize(new Dimension(30, 20));
+		total.setHorizontalAlignment(SwingConstants.CENTER);
+		total.setEditable(false);
+		total.setColumns(3);
+		total.setBorder(new LineBorder(Color.BLACK));
+		GridBagConstraints gbc_total = new GridBagConstraints();
+		gbc_total.insets = new Insets(0, 0, 0, 4);
+		gbc_total.fill = GridBagConstraints.HORIZONTAL;
+		gbc_total.gridx = 1;
+		gbc_total.gridy = 0;
+		panel.add(total, gbc_total);
+		
+		JLabel label = new JLabel("=");
+		GridBagConstraints gbc_label = new GridBagConstraints();
+		gbc_label.insets = new Insets(0, 0, 0, 4);
+		gbc_label.gridx = 2;
+		gbc_label.gridy = 0;
+		panel.add(label, gbc_label);
+		
+		JLabel label_7 = new JLabel("10+");
+		label_7.setFont(new Font("Tahoma", Font.BOLD, 14));
+		GridBagConstraints gbc_label_7 = new GridBagConstraints();
+		gbc_label_7.insets = new Insets(0, 0, 0, 4);
+		gbc_label_7.anchor = GridBagConstraints.EAST;
+		gbc_label_7.gridx = 3;
+		gbc_label_7.gridy = 0;
+		panel.add(label_7, gbc_label_7);
+		
+		armor = new JTextField();
+		armor.setToolTipText("Armor Bonus");
+		armor.setPreferredSize(new Dimension(25, 20));
+		armor.setMinimumSize(new Dimension(25, 20));
+		armor.setMaximumSize(new Dimension(25, 20));
+		armor.setHorizontalAlignment(SwingConstants.CENTER);
+		armor.setEditable(false);
+		armor.setColumns(3);
+		armor.setBorder(new LineBorder(Color.BLACK));
+		GridBagConstraints gbc_armor = new GridBagConstraints();
+		gbc_armor.insets = new Insets(0, 0, 0, 4);
+		gbc_armor.fill = GridBagConstraints.HORIZONTAL;
+		gbc_armor.gridx = 4;
+		gbc_armor.gridy = 0;
+		panel.add(armor, gbc_armor);
+		
+		JLabel label_1 = new JLabel("+");
+		GridBagConstraints gbc_label_1 = new GridBagConstraints();
+		gbc_label_1.insets = new Insets(0, 0, 0, 4);
+		gbc_label_1.anchor = GridBagConstraints.EAST;
+		gbc_label_1.gridx = 5;
+		gbc_label_1.gridy = 0;
+		panel.add(label_1, gbc_label_1);
+		
+		shield = new JTextField();
+		shield.setToolTipText("Shield Bonus");
+		shield.setPreferredSize(new Dimension(25, 20));
+		shield.setMinimumSize(new Dimension(25, 20));
+		shield.setMaximumSize(new Dimension(25, 20));
+		shield.setHorizontalAlignment(SwingConstants.CENTER);
+		shield.setEditable(false);
+		shield.setColumns(3);
+		shield.setBorder(new LineBorder(Color.BLACK));
+		GridBagConstraints gbc_shield = new GridBagConstraints();
+		gbc_shield.insets = new Insets(0, 0, 0, 4);
+		gbc_shield.fill = GridBagConstraints.HORIZONTAL;
+		gbc_shield.gridx = 6;
+		gbc_shield.gridy = 0;
+		panel.add(shield, gbc_shield);
+		
+		JLabel label_2 = new JLabel("+");
+		GridBagConstraints gbc_label_2 = new GridBagConstraints();
+		gbc_label_2.insets = new Insets(0, 0, 0, 4);
+		gbc_label_2.anchor = GridBagConstraints.EAST;
+		gbc_label_2.gridx = 7;
+		gbc_label_2.gridy = 0;
+		panel.add(label_2, gbc_label_2);
+		
+		dexterity = new JTextField();
+		dexterity.setToolTipText("Dexterity Modifier");
+		dexterity.setPreferredSize(new Dimension(25, 20));
+		dexterity.setMinimumSize(new Dimension(25, 20));
+		dexterity.setMaximumSize(new Dimension(25, 20));
+		dexterity.setHorizontalAlignment(SwingConstants.CENTER);
+		dexterity.setEditable(false);
+		dexterity.setColumns(3);
+		dexterity.setBorder(new LineBorder(Color.BLACK));
+		GridBagConstraints gbc_dexterity = new GridBagConstraints();
+		gbc_dexterity.insets = new Insets(0, 0, 0, 4);
+		gbc_dexterity.fill = GridBagConstraints.HORIZONTAL;
+		gbc_dexterity.gridx = 8;
+		gbc_dexterity.gridy = 0;
+		panel.add(dexterity, gbc_dexterity);
+		
+		JLabel label_3 = new JLabel("+");
+		GridBagConstraints gbc_label_3 = new GridBagConstraints();
+		gbc_label_3.insets = new Insets(0, 0, 0, 4);
+		gbc_label_3.anchor = GridBagConstraints.EAST;
+		gbc_label_3.gridx = 9;
+		gbc_label_3.gridy = 0;
+		panel.add(label_3, gbc_label_3);
+		
+		size = new JTextField();
+		size.setToolTipText("Size Modifier");
+		size.setPreferredSize(new Dimension(25, 20));
+		size.setMinimumSize(new Dimension(25, 20));
+		size.setMaximumSize(new Dimension(25, 20));
+		size.setHorizontalAlignment(SwingConstants.CENTER);
+		size.setEditable(false);
+		size.setColumns(3);
+		size.setBorder(new LineBorder(Color.BLACK));
+		GridBagConstraints gbc_size = new GridBagConstraints();
+		gbc_size.insets = new Insets(0, 0, 0, 4);
+		gbc_size.fill = GridBagConstraints.HORIZONTAL;
+		gbc_size.gridx = 10;
+		gbc_size.gridy = 0;
+		panel.add(size, gbc_size);
+		
+		JLabel label_4 = new JLabel("+");
+		GridBagConstraints gbc_label_4 = new GridBagConstraints();
+		gbc_label_4.insets = new Insets(0, 0, 0, 4);
+		gbc_label_4.anchor = GridBagConstraints.EAST;
+		gbc_label_4.gridx = 11;
+		gbc_label_4.gridy = 0;
+		panel.add(label_4, gbc_label_4);
+		
+		natural = new JTextField();
+		natural.setToolTipText("Natural Armor");
+		natural.setPreferredSize(new Dimension(25, 20));
+		natural.setMinimumSize(new Dimension(25, 20));
+		natural.setMaximumSize(new Dimension(25, 20));
+		natural.setHorizontalAlignment(SwingConstants.CENTER);
+		natural.setEditable(false);
+		natural.setColumns(3);
+		natural.setBorder(new LineBorder(Color.BLACK));
+		GridBagConstraints gbc_natural = new GridBagConstraints();
+		gbc_natural.insets = new Insets(0, 0, 0, 4);
+		gbc_natural.fill = GridBagConstraints.HORIZONTAL;
+		gbc_natural.gridx = 12;
+		gbc_natural.gridy = 0;
+		panel.add(natural, gbc_natural);
+		
+		JLabel label_5 = new JLabel("+");
+		GridBagConstraints gbc_label_5 = new GridBagConstraints();
+		gbc_label_5.insets = new Insets(0, 0, 0, 4);
+		gbc_label_5.anchor = GridBagConstraints.EAST;
+		gbc_label_5.gridx = 13;
+		gbc_label_5.gridy = 0;
+		panel.add(label_5, gbc_label_5);
+		
+		deflection = new JTextField();
+		deflection.setToolTipText("Deflection Modifier");
+		deflection.setPreferredSize(new Dimension(25, 20));
+		deflection.setMinimumSize(new Dimension(25, 20));
+		deflection.setMaximumSize(new Dimension(25, 20));
+		deflection.setHorizontalAlignment(SwingConstants.CENTER);
+		deflection.setEditable(false);
+		deflection.setColumns(3);
+		deflection.setBorder(new LineBorder(Color.BLACK));
+		GridBagConstraints gbc_deflection = new GridBagConstraints();
+		gbc_deflection.insets = new Insets(0, 0, 0, 4);
+		gbc_deflection.fill = GridBagConstraints.HORIZONTAL;
+		gbc_deflection.gridx = 14;
+		gbc_deflection.gridy = 0;
+		panel.add(deflection, gbc_deflection);
+		
+		JLabel label_6 = new JLabel("+");
+		GridBagConstraints gbc_label_6 = new GridBagConstraints();
+		gbc_label_6.insets = new Insets(0, 0, 0, 4);
+		gbc_label_6.anchor = GridBagConstraints.EAST;
+		gbc_label_6.gridx = 15;
+		gbc_label_6.gridy = 0;
+		panel.add(label_6, gbc_label_6);
+		
+		misc = new JTextField();
+		misc.setToolTipText("Miscellaneous Modifier");
+		misc.setPreferredSize(new Dimension(25, 20));
+		misc.setMinimumSize(new Dimension(25, 20));
+		misc.setMaximumSize(new Dimension(25, 20));
+		misc.setHorizontalAlignment(SwingConstants.CENTER);
+		misc.setEditable(false);
+		misc.setColumns(3);
+		misc.setBorder(new LineBorder(Color.BLACK));
+		GridBagConstraints gbc_misc = new GridBagConstraints();
+		gbc_misc.fill = GridBagConstraints.HORIZONTAL;
+		gbc_misc.gridx = 16;
+		gbc_misc.gridy = 0;
+		panel.add(misc, gbc_misc);
+		
+		JPanel panel_1 = new JPanel();
+		panel_1.setOpaque(false);
+		GridBagConstraints gbc_panel_1 = new GridBagConstraints();
+		gbc_panel_1.fill = GridBagConstraints.BOTH;
+		gbc_panel_1.gridx = 0;
+		gbc_panel_1.gridy = 1;
+		add(panel_1, gbc_panel_1);
+		GridBagLayout gbl_panel_1 = new GridBagLayout();
+		gbl_panel_1.columnWidths = new int[]{0, 0, 0, 0, 0};
+		gbl_panel_1.rowHeights = new int[]{0, 0};
+		gbl_panel_1.columnWeights = new double[]{0.0, 0.0, 0.0, 0.0, Double.MIN_VALUE};
+		gbl_panel_1.rowWeights = new double[]{0.0, Double.MIN_VALUE};
+		panel_1.setLayout(gbl_panel_1);
+		
+		JLabel lblTouch = new JLabel("TOUCH");
+		lblTouch.setToolTipText("Armor Class");
+		lblTouch.setPreferredSize(new Dimension(80, 25));
+		lblTouch.setOpaque(true);
+		lblTouch.setMinimumSize(new Dimension(50, 25));
+		lblTouch.setMaximumSize(new Dimension(50, 25));
+		lblTouch.setHorizontalAlignment(SwingConstants.CENTER);
+		lblTouch.setForeground(Color.WHITE);
+		lblTouch.setFont(new Font("Tahoma", Font.BOLD, 18));
+		lblTouch.setBorder(new LineBorder(Color.WHITE));
+		lblTouch.setBackground(Color.BLACK);
+		GridBagConstraints gbc_lblTouch = new GridBagConstraints();
+		gbc_lblTouch.anchor = GridBagConstraints.EAST;
+		gbc_lblTouch.insets = new Insets(0, 0, 0, 5);
+		gbc_lblTouch.gridx = 0;
+		gbc_lblTouch.gridy = 0;
+		panel_1.add(lblTouch, gbc_lblTouch);
+		
+		touch = new JTextField();
+		touch.setToolTipText("Touch AC");
+		touch.setPreferredSize(new Dimension(30, 20));
+		touch.setMinimumSize(new Dimension(30, 20));
+		touch.setMaximumSize(new Dimension(30, 20));
+		touch.setHorizontalAlignment(SwingConstants.CENTER);
+		touch.setEditable(false);
+		touch.setColumns(3);
+		touch.setBorder(new LineBorder(Color.BLACK));
+		GridBagConstraints gbc_touch = new GridBagConstraints();
+		gbc_touch.insets = new Insets(0, 0, 0, 5);
+		gbc_touch.fill = GridBagConstraints.HORIZONTAL;
+		gbc_touch.gridx = 1;
+		gbc_touch.gridy = 0;
+		panel_1.add(touch, gbc_touch);
+		
+		JLabel lblFlatfooted = new JLabel("FLAT-FOOTED");
+		lblFlatfooted.setToolTipText("Armor Class");
+		lblFlatfooted.setPreferredSize(new Dimension(140, 25));
+		lblFlatfooted.setOpaque(true);
+		lblFlatfooted.setMinimumSize(new Dimension(50, 25));
+		lblFlatfooted.setMaximumSize(new Dimension(50, 25));
+		lblFlatfooted.setHorizontalAlignment(SwingConstants.CENTER);
+		lblFlatfooted.setForeground(Color.WHITE);
+		lblFlatfooted.setFont(new Font("Tahoma", Font.BOLD, 18));
+		lblFlatfooted.setBorder(new LineBorder(Color.WHITE));
+		lblFlatfooted.setBackground(Color.BLACK);
+		GridBagConstraints gbc_lblFlatfooted = new GridBagConstraints();
+		gbc_lblFlatfooted.insets = new Insets(0, 0, 0, 5);
+		gbc_lblFlatfooted.anchor = GridBagConstraints.EAST;
+		gbc_lblFlatfooted.gridx = 2;
+		gbc_lblFlatfooted.gridy = 0;
+		panel_1.add(lblFlatfooted, gbc_lblFlatfooted);
+		
+		flatFoot = new JTextField();
+		flatFoot.setToolTipText("Flat-Footed AC");
+		flatFoot.setPreferredSize(new Dimension(30, 20));
+		flatFoot.setMinimumSize(new Dimension(30, 20));
+		flatFoot.setMaximumSize(new Dimension(30, 20));
+		flatFoot.setHorizontalAlignment(SwingConstants.CENTER);
+		flatFoot.setEditable(false);
+		flatFoot.setColumns(3);
+		flatFoot.setBorder(new LineBorder(Color.BLACK));
+		GridBagConstraints gbc_flatFoot = new GridBagConstraints();
+		gbc_flatFoot.fill = GridBagConstraints.HORIZONTAL;
+		gbc_flatFoot.gridx = 3;
+		gbc_flatFoot.gridy = 0;
+		panel_1.add(flatFoot, gbc_flatFoot);
+	}
+
+}

+ 251 - 0
src/org/leumasjaffe/charsheet/view/summary/AttackLine.java

@@ -0,0 +1,251 @@
+package org.leumasjaffe.charsheet.view.summary;
+
+import javax.swing.JPanel;
+import java.awt.GridBagLayout;
+import javax.swing.JLabel;
+import java.awt.GridBagConstraints;
+import java.awt.Color;
+import javax.swing.border.LineBorder;
+
+import org.leumasjaffe.charsheet.entity.AbilityScores;
+import org.leumasjaffe.charsheet.entity.DDCharacter;
+import org.leumasjaffe.charsheet.entity.viewable.IntValue;
+import org.leumasjaffe.charsheet.observer.ObservableListener;
+import org.leumasjaffe.charsheet.observer.helper.AbilModStringify;
+import org.leumasjaffe.charsheet.view.StringHelper;
+
+import lombok.AccessLevel;
+import lombok.experimental.FieldDefaults;
+import lombok.experimental.NonFinal;
+
+import java.awt.Font;
+import javax.swing.SwingConstants;
+import java.awt.Dimension;
+import java.awt.Insets;
+import javax.swing.JTextField;
+import java.awt.Component;
+import javax.swing.Box;
+
+@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
+public class AttackLine extends JPanel {
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = 1L;
+	@NonFinal DDCharacter model;
+	
+	JTextField grappleTtl;
+	JTextField grappleBab;
+	JTextField grappleMisc;
+	JTextField grappleSize;
+	JTextField baseAttack;
+	JTextField grappleStrength;
+	
+	ObservableListener<IntValue> gStrObserver;
+
+	public AttackLine() {
+		setPreferredSize(new Dimension(600, 25));
+		setOpaque(false);
+		GridBagLayout gridBagLayout = new GridBagLayout();
+		gridBagLayout.columnWidths = new int[]{200, 0, 0, 120, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+		gridBagLayout.rowHeights = new int[]{0, 0};
+		gridBagLayout.columnWeights = new double[]{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, Double.MIN_VALUE};
+		gridBagLayout.rowWeights = new double[]{0.0, Double.MIN_VALUE};
+		setLayout(gridBagLayout);
+		
+		JLabel lblBaseAttackBonus = new JLabel("BASE ATTACK BONUS");
+		lblBaseAttackBonus.setToolTipText("");
+		lblBaseAttackBonus.setPreferredSize(new Dimension(200, 25));
+		lblBaseAttackBonus.setOpaque(true);
+		lblBaseAttackBonus.setMinimumSize(new Dimension(50, 25));
+		lblBaseAttackBonus.setMaximumSize(new Dimension(50, 25));
+		lblBaseAttackBonus.setHorizontalAlignment(SwingConstants.CENTER);
+		lblBaseAttackBonus.setForeground(Color.WHITE);
+		lblBaseAttackBonus.setFont(new Font("Tahoma", Font.BOLD, 18));
+		lblBaseAttackBonus.setBorder(new LineBorder(Color.WHITE));
+		lblBaseAttackBonus.setBackground(Color.BLACK);
+		GridBagConstraints gbc_lblBaseAttackBonus = new GridBagConstraints();
+		gbc_lblBaseAttackBonus.fill = GridBagConstraints.HORIZONTAL;
+		gbc_lblBaseAttackBonus.insets = new Insets(0, 0, 0, 5);
+		gbc_lblBaseAttackBonus.gridx = 0;
+		gbc_lblBaseAttackBonus.gridy = 0;
+		add(lblBaseAttackBonus, gbc_lblBaseAttackBonus);
+		
+		baseAttack = new JTextField();
+		baseAttack.setToolTipText("");
+		baseAttack.setPreferredSize(new Dimension(30, 20));
+		baseAttack.setMinimumSize(new Dimension(30, 20));
+		baseAttack.setMaximumSize(new Dimension(30, 20));
+		baseAttack.setHorizontalAlignment(SwingConstants.CENTER);
+		baseAttack.setEditable(false);
+		baseAttack.setColumns(3);
+		baseAttack.setBorder(new LineBorder(Color.BLACK));
+		GridBagConstraints gbc_baseAttack = new GridBagConstraints();
+		gbc_baseAttack.insets = new Insets(0, 0, 0, 5);
+		gbc_baseAttack.fill = GridBagConstraints.HORIZONTAL;
+		gbc_baseAttack.gridx = 1;
+		gbc_baseAttack.gridy = 0;
+		add(baseAttack, gbc_baseAttack);
+		
+		Component horizontalStrut = Box.createHorizontalStrut(20);
+		horizontalStrut.setPreferredSize(new Dimension(60, 0));
+		GridBagConstraints gbc_horizontalStrut = new GridBagConstraints();
+		gbc_horizontalStrut.insets = new Insets(0, 0, 0, 5);
+		gbc_horizontalStrut.gridx = 2;
+		gbc_horizontalStrut.gridy = 0;
+		add(horizontalStrut, gbc_horizontalStrut);
+		
+		JLabel lblGrapple = new JLabel("GRAPPLE");
+		lblGrapple.setToolTipText("");
+		lblGrapple.setPreferredSize(new Dimension(120, 25));
+		lblGrapple.setOpaque(true);
+		lblGrapple.setMinimumSize(new Dimension(50, 25));
+		lblGrapple.setMaximumSize(new Dimension(50, 25));
+		lblGrapple.setHorizontalAlignment(SwingConstants.CENTER);
+		lblGrapple.setForeground(Color.WHITE);
+		lblGrapple.setFont(new Font("Tahoma", Font.BOLD, 18));
+		lblGrapple.setBorder(new LineBorder(Color.WHITE));
+		lblGrapple.setBackground(Color.BLACK);
+		GridBagConstraints gbc_lblGrapple = new GridBagConstraints();
+		gbc_lblGrapple.fill = GridBagConstraints.HORIZONTAL;
+		gbc_lblGrapple.insets = new Insets(0, 0, 0, 5);
+		gbc_lblGrapple.gridx = 3;
+		gbc_lblGrapple.gridy = 0;
+		add(lblGrapple, gbc_lblGrapple);
+		
+		grappleTtl = new JTextField();
+		grappleTtl.setToolTipText("Total Grapple");
+		grappleTtl.setPreferredSize(new Dimension(30, 20));
+		grappleTtl.setMinimumSize(new Dimension(30, 20));
+		grappleTtl.setMaximumSize(new Dimension(30, 20));
+		grappleTtl.setHorizontalAlignment(SwingConstants.CENTER);
+		grappleTtl.setEditable(false);
+		grappleTtl.setColumns(3);
+		grappleTtl.setBorder(new LineBorder(Color.BLACK));
+		GridBagConstraints gbc_grappleTtl = new GridBagConstraints();
+		gbc_grappleTtl.insets = new Insets(0, 0, 0, 5);
+		gbc_grappleTtl.fill = GridBagConstraints.HORIZONTAL;
+		gbc_grappleTtl.gridx = 4;
+		gbc_grappleTtl.gridy = 0;
+		add(grappleTtl, gbc_grappleTtl);
+		
+		JLabel label = new JLabel("=");
+		GridBagConstraints gbc_label = new GridBagConstraints();
+		gbc_label.insets = new Insets(0, 0, 0, 5);
+		gbc_label.anchor = GridBagConstraints.EAST;
+		gbc_label.gridx = 5;
+		gbc_label.gridy = 0;
+		add(label, gbc_label);
+		
+		grappleBab = new JTextField();
+		grappleBab.setDocument(baseAttack.getDocument());
+		grappleBab.setToolTipText("Base Attack Bonus");
+		grappleBab.setPreferredSize(new Dimension(30, 20));
+		grappleBab.setMinimumSize(new Dimension(30, 20));
+		grappleBab.setMaximumSize(new Dimension(30, 20));
+		grappleBab.setHorizontalAlignment(SwingConstants.CENTER);
+		grappleBab.setEditable(false);
+		grappleBab.setColumns(3);
+		grappleBab.setBorder(new LineBorder(Color.BLACK));
+		GridBagConstraints gbc_grappleBab = new GridBagConstraints();
+		gbc_grappleBab.insets = new Insets(0, 0, 0, 5);
+		gbc_grappleBab.fill = GridBagConstraints.HORIZONTAL;
+		gbc_grappleBab.gridx = 6;
+		gbc_grappleBab.gridy = 0;
+		add(grappleBab, gbc_grappleBab);
+		
+		JLabel label_1 = new JLabel("+");
+		GridBagConstraints gbc_label_1 = new GridBagConstraints();
+		gbc_label_1.insets = new Insets(0, 0, 0, 5);
+		gbc_label_1.anchor = GridBagConstraints.EAST;
+		gbc_label_1.gridx = 7;
+		gbc_label_1.gridy = 0;
+		add(label_1, gbc_label_1);
+		
+		grappleStrength = new JTextField();
+		grappleStrength.setToolTipText("Strength Modifier");
+		grappleStrength.setPreferredSize(new Dimension(30, 20));
+		grappleStrength.setMinimumSize(new Dimension(30, 20));
+		grappleStrength.setMaximumSize(new Dimension(30, 20));
+		grappleStrength.setHorizontalAlignment(SwingConstants.CENTER);
+		grappleStrength.setEditable(false);
+		grappleStrength.setColumns(3);
+		grappleStrength.setBorder(new LineBorder(Color.BLACK));
+		GridBagConstraints gbc_grappleStrength = new GridBagConstraints();
+		gbc_grappleStrength.insets = new Insets(0, 0, 0, 5);
+		gbc_grappleStrength.fill = GridBagConstraints.HORIZONTAL;
+		gbc_grappleStrength.gridx = 8;
+		gbc_grappleStrength.gridy = 0;
+		add(grappleStrength, gbc_grappleStrength);
+		
+		JLabel label_3 = new JLabel("+");
+		GridBagConstraints gbc_label_3 = new GridBagConstraints();
+		gbc_label_3.insets = new Insets(0, 0, 0, 5);
+		gbc_label_3.anchor = GridBagConstraints.EAST;
+		gbc_label_3.gridx = 9;
+		gbc_label_3.gridy = 0;
+		add(label_3, gbc_label_3);
+		
+		grappleSize = new JTextField();
+		grappleSize.setToolTipText("Size Modifier");
+		grappleSize.setPreferredSize(new Dimension(30, 20));
+		grappleSize.setMinimumSize(new Dimension(30, 20));
+		grappleSize.setMaximumSize(new Dimension(30, 20));
+		grappleSize.setHorizontalAlignment(SwingConstants.CENTER);
+		grappleSize.setEditable(false);
+		grappleSize.setColumns(3);
+		grappleSize.setBorder(new LineBorder(Color.BLACK));
+		GridBagConstraints gbc_grappleSize = new GridBagConstraints();
+		gbc_grappleSize.insets = new Insets(0, 0, 0, 5);
+		gbc_grappleSize.fill = GridBagConstraints.HORIZONTAL;
+		gbc_grappleSize.gridx = 10;
+		gbc_grappleSize.gridy = 0;
+		add(grappleSize, gbc_grappleSize);
+		
+		JLabel label_2 = new JLabel("+");
+		GridBagConstraints gbc_label_2 = new GridBagConstraints();
+		gbc_label_2.insets = new Insets(0, 0, 0, 5);
+		gbc_label_2.anchor = GridBagConstraints.EAST;
+		gbc_label_2.gridx = 11;
+		gbc_label_2.gridy = 0;
+		add(label_2, gbc_label_2);
+		
+		grappleMisc = new JTextField();
+		grappleMisc.setToolTipText("Miscellaneous Modifier");
+		grappleMisc.setPreferredSize(new Dimension(30, 20));
+		grappleMisc.setMinimumSize(new Dimension(30, 20));
+		grappleMisc.setMaximumSize(new Dimension(30, 20));
+		grappleMisc.setHorizontalAlignment(SwingConstants.CENTER);
+		grappleMisc.setEditable(false);
+		grappleMisc.setColumns(3);
+		grappleMisc.setBorder(new LineBorder(Color.BLACK));
+		GridBagConstraints gbc_grappleMisc = new GridBagConstraints();
+		gbc_grappleMisc.fill = GridBagConstraints.HORIZONTAL;
+		gbc_grappleMisc.gridx = 12;
+		gbc_grappleMisc.gridy = 0;
+		add(grappleMisc, gbc_grappleMisc);
+		
+		gStrObserver = new ObservableListener<>(grappleStrength, 
+				new AbilModStringify());
+	}
+
+	public void setModel(DDCharacter model) {
+		this.model = model;
+		final int bab = this.model.getBaseAttack();
+		final int size = this.model.getSize().value().modifier;
+		final int misc = 0;
+		this.baseAttack.setText(StringHelper.toString(bab));
+		gStrObserver.setObserved(model.getAbilities().getBase().getStr());
+		this.grappleSize.setText(StringHelper.toString(size));
+		this.grappleMisc.setText(StringHelper.toString(misc));
+	}
+	
+	public void updateModel() {
+		final int bab = this.model.getBaseAttack();
+		final int str = AbilityScores.modifer(this.model.getAbilities().getBase().getStr().value());
+		final int size = this.model.getSize().value().modifier;
+		final int misc = 0;
+		this.grappleTtl.setText(StringHelper.toString(bab + str + size + misc));
+	}
+
+}

+ 65 - 0
src/org/leumasjaffe/charsheet/view/summary/DamageReductionLine.java

@@ -0,0 +1,65 @@
+package org.leumasjaffe.charsheet.view.summary;
+
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+
+import java.awt.GridBagLayout;
+import javax.swing.JLabel;
+import java.awt.GridBagConstraints;
+import java.awt.Color;
+import javax.swing.border.LineBorder;
+import java.awt.Font;
+import javax.swing.SwingConstants;
+import java.awt.Dimension;
+import java.awt.Insets;
+
+public class DamageReductionLine extends JPanel {
+
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = 1L;
+
+	public DamageReductionLine() {
+		setOpaque(false);
+		GridBagLayout gridBagLayout = new GridBagLayout();
+		gridBagLayout.columnWidths = new int[]{0, 100, 0};
+		gridBagLayout.rowHeights = new int[]{0, 0};
+		gridBagLayout.columnWeights = new double[]{0.0, 0.0, Double.MIN_VALUE};
+		gridBagLayout.rowWeights = new double[]{0.0, Double.MIN_VALUE};
+		setLayout(gridBagLayout);
+		
+		JLabel lblSpellResistence = new JLabel("DR");
+		lblSpellResistence.setToolTipText("Damage Reduction");
+		lblSpellResistence.setPreferredSize(new Dimension(50, 25));
+		lblSpellResistence.setOpaque(true);
+		lblSpellResistence.setMinimumSize(new Dimension(50, 25));
+		lblSpellResistence.setMaximumSize(new Dimension(50, 25));
+		lblSpellResistence.setHorizontalAlignment(SwingConstants.CENTER);
+		lblSpellResistence.setForeground(Color.WHITE);
+		lblSpellResistence.setFont(new Font("Tahoma", Font.BOLD, 18));
+		lblSpellResistence.setBorder(new LineBorder(Color.WHITE));
+		lblSpellResistence.setBackground(Color.BLACK);
+		GridBagConstraints gbc_lblSpellResistence = new GridBagConstraints();
+		gbc_lblSpellResistence.insets = new Insets(0, 0, 0, 5);
+		gbc_lblSpellResistence.anchor = GridBagConstraints.EAST;
+		gbc_lblSpellResistence.gridx = 0;
+		gbc_lblSpellResistence.gridy = 0;
+		add(lblSpellResistence, gbc_lblSpellResistence);
+		
+		JTextField numberTextField = new JTextField();
+		numberTextField.setToolTipText("");
+		numberTextField.setPreferredSize(new Dimension(100, 20));
+		numberTextField.setMinimumSize(new Dimension(100, 20));
+		numberTextField.setMaximumSize(new Dimension(100, 20));
+		numberTextField.setHorizontalAlignment(SwingConstants.CENTER);
+		numberTextField.setColumns(3);
+		numberTextField.setBorder(new LineBorder(Color.BLACK));
+		GridBagConstraints gbc_numberTextField = new GridBagConstraints();
+		gbc_numberTextField.fill = GridBagConstraints.HORIZONTAL;
+		gbc_numberTextField.gridx = 1;
+		gbc_numberTextField.gridy = 0;
+		add(numberTextField, gbc_numberTextField);
+		// TODO Auto-generated constructor stub
+	}
+}

+ 387 - 0
src/org/leumasjaffe/charsheet/view/summary/DescriptionPanel.java

@@ -0,0 +1,387 @@
+package org.leumasjaffe.charsheet.view.summary;
+
+import javax.swing.JPanel;
+import java.awt.GridBagLayout;
+import java.awt.GridBagConstraints;
+import java.awt.Insets;
+import javax.swing.JTextField;
+import javax.swing.border.TitledBorder;
+
+import org.leumasjaffe.charsheet.entity.DDCharacter;
+import org.leumasjaffe.charsheet.view.StringHelper;
+
+import javax.swing.border.Border;
+import javax.swing.border.MatteBorder;
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.Dimension;
+
+public class DescriptionPanel extends JPanel {
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = 1L;
+	private JTextField name;
+	private JTextField level;
+	private JTextField height;
+	private JTextField gender;
+	private JTextField age;
+	private JTextField size;
+	private JTextField player;
+	private JTextField deity;
+	private JTextField alignment;
+	private JTextField race;
+	private JTextField skin;
+	private JTextField hair;
+	private JTextField eyes;
+	private JTextField weight;
+	
+	private DDCharacter model;
+	
+	public DescriptionPanel() {
+		setMinimumSize(new Dimension(600, 120));
+		setMaximumSize(new Dimension(600, 120));
+		setPreferredSize(new Dimension(600, 120));
+		setOpaque(false);
+		GridBagLayout gridBagLayout = new GridBagLayout();
+		gridBagLayout.columnWidths = new int[]{0, 0, 0};
+		gridBagLayout.rowHeights = new int[]{0, 0};
+		gridBagLayout.columnWeights = new double[]{1.0, 1.0, Double.MIN_VALUE};
+		gridBagLayout.rowWeights = new double[]{1.0, Double.MIN_VALUE};
+		setLayout(gridBagLayout);
+		
+		JPanel panel = new JPanel();
+		panel.setOpaque(false);
+		GridBagConstraints gbc_panel = new GridBagConstraints();
+		gbc_panel.insets = new Insets(0, 0, 0, 5);
+		gbc_panel.fill = GridBagConstraints.BOTH;
+		gbc_panel.gridx = 0;
+		gbc_panel.gridy = 0;
+		add(panel, gbc_panel);
+		GridBagLayout gbl_panel = new GridBagLayout();
+		gbl_panel.columnWidths = new int[]{0, 0};
+		gbl_panel.rowHeights = new int[]{0, 0, 0, 0};
+		gbl_panel.columnWeights = new double[]{1.0, Double.MIN_VALUE};
+		gbl_panel.rowWeights = new double[]{1.0, 1.0, 1.0, Double.MIN_VALUE};
+		panel.setLayout(gbl_panel);
+		
+		JPanel panel_4 = new JPanel();
+		panel_4.setPreferredSize(new Dimension(10, 30));
+		panel_4.setMinimumSize(new Dimension(10, 30));
+		panel_4.setOpaque(false);
+		GridBagConstraints gbc_panel_4 = new GridBagConstraints();
+		gbc_panel_4.insets = new Insets(0, 0, 5, 0);
+		gbc_panel_4.fill = GridBagConstraints.BOTH;
+		gbc_panel_4.gridx = 0;
+		gbc_panel_4.gridy = 0;
+		panel.add(panel_4, gbc_panel_4);
+		GridBagLayout gbl_panel_4 = new GridBagLayout();
+		gbl_panel_4.columnWidths = new int[]{0, 0};
+		gbl_panel_4.rowHeights = new int[]{0, 0};
+		gbl_panel_4.columnWeights = new double[]{1.0, Double.MIN_VALUE};
+		gbl_panel_4.rowWeights = new double[]{1.0, Double.MIN_VALUE};
+		panel_4.setLayout(gbl_panel_4);
+		
+		final Border lineBorder = new MatteBorder(0, 0, 1, 0, Color.BLACK);
+		final int borderAlign = TitledBorder.LEADING;
+		final int titlePos = TitledBorder.BELOW_BOTTOM;
+		final Font titleFont = new Font("Tahoma", Font.PLAIN, 8);
+		final Color titleColor = Color.BLACK;
+		
+		name = new JTextField();
+		name.setMaximumSize(new Dimension(2147483647, 30));
+		name.setMinimumSize(new Dimension(6, 30));
+		name.setPreferredSize(new Dimension(6, 30));
+		name.setBorder(new TitledBorder(lineBorder, "CHARACTER NAME", borderAlign, titlePos, titleFont, titleColor));
+		GridBagConstraints gbc_name = new GridBagConstraints();
+		gbc_name.fill = GridBagConstraints.HORIZONTAL;
+		gbc_name.gridx = 0;
+		gbc_name.gridy = 0;
+		panel_4.add(name, gbc_name);
+		name.setColumns(10);
+		
+		JPanel panel_3 = new JPanel();
+		panel_3.setPreferredSize(new Dimension(10, 30));
+		panel_3.setMinimumSize(new Dimension(10, 30));
+		panel_3.setOpaque(false);
+		GridBagConstraints gbc_panel_3 = new GridBagConstraints();
+		gbc_panel_3.insets = new Insets(0, 0, 5, 0);
+		gbc_panel_3.fill = GridBagConstraints.BOTH;
+		gbc_panel_3.gridx = 0;
+		gbc_panel_3.gridy = 1;
+		panel.add(panel_3, gbc_panel_3);
+		GridBagLayout gbl_panel_3 = new GridBagLayout();
+		gbl_panel_3.columnWidths = new int[]{0, 0};
+		gbl_panel_3.rowHeights = new int[]{0, 0};
+		gbl_panel_3.columnWeights = new double[]{1.0, Double.MIN_VALUE};
+		gbl_panel_3.rowWeights = new double[]{1.0, Double.MIN_VALUE};
+		panel_3.setLayout(gbl_panel_3);
+		
+		level = new JTextField();
+		level.setEditable(false);
+		level.setMaximumSize(new Dimension(2147483647, 30));
+		level.setMinimumSize(new Dimension(6, 30));
+		level.setPreferredSize(new Dimension(6, 30));
+		level.setColumns(10);
+		level.setBorder(new TitledBorder(lineBorder, "CLASS AND LEVEL", borderAlign, titlePos, titleFont, titleColor));
+		GridBagConstraints gbc_level = new GridBagConstraints();
+		gbc_level.fill = GridBagConstraints.HORIZONTAL;
+		gbc_level.gridx = 0;
+		gbc_level.gridy = 0;
+		panel_3.add(level, gbc_level);
+		
+		JPanel panel_2 = new JPanel();
+		panel_2.setPreferredSize(new Dimension(10, 30));
+		panel_2.setMinimumSize(new Dimension(10, 30));
+		panel_2.setOpaque(false);
+		GridBagConstraints gbc_panel_2 = new GridBagConstraints();
+		gbc_panel_2.fill = GridBagConstraints.BOTH;
+		gbc_panel_2.gridx = 0;
+		gbc_panel_2.gridy = 2;
+		panel.add(panel_2, gbc_panel_2);
+		GridBagLayout gbl_panel_2 = new GridBagLayout();
+		gbl_panel_2.columnWidths = new int[]{0, 0, 0, 0, 0};
+		gbl_panel_2.rowHeights = new int[]{0, 0};
+		gbl_panel_2.columnWeights = new double[]{1.0, 1.0, 1.0, 1.0, Double.MIN_VALUE};
+		gbl_panel_2.rowWeights = new double[]{1.0, Double.MIN_VALUE};
+		panel_2.setLayout(gbl_panel_2);
+		
+		size = new JTextField();
+		size.setMaximumSize(new Dimension(2147483647, 30));
+		size.setMinimumSize(new Dimension(6, 30));
+		size.setPreferredSize(new Dimension(6, 30));
+		size.setColumns(10);
+		size.setBorder(new TitledBorder(lineBorder, "SIZE", borderAlign, titlePos, titleFont, titleColor));
+		GridBagConstraints gbc_size = new GridBagConstraints();
+		gbc_size.insets = new Insets(0, 0, 0, 5);
+		gbc_size.fill = GridBagConstraints.HORIZONTAL;
+		gbc_size.gridx = 0;
+		gbc_size.gridy = 0;
+		panel_2.add(size, gbc_size);
+		
+		age = new JTextField();
+		age.setMaximumSize(new Dimension(2147483647, 30));
+		age.setMinimumSize(new Dimension(6, 30));
+		age.setPreferredSize(new Dimension(6, 30));
+		age.setColumns(10);
+		age.setBorder(new TitledBorder(lineBorder, "AGE", borderAlign, titlePos, titleFont, titleColor));
+		GridBagConstraints gbc_age = new GridBagConstraints();
+		gbc_age.insets = new Insets(0, 0, 0, 5);
+		gbc_age.fill = GridBagConstraints.HORIZONTAL;
+		gbc_age.gridx = 1;
+		gbc_age.gridy = 0;
+		panel_2.add(age, gbc_age);
+		
+		gender = new JTextField();
+		gender.setMaximumSize(new Dimension(2147483647, 30));
+		gender.setMinimumSize(new Dimension(6, 30));
+		gender.setPreferredSize(new Dimension(6, 30));
+		gender.setColumns(10);
+		gender.setBorder(new TitledBorder(lineBorder, "GENDER", borderAlign, titlePos, titleFont, titleColor));
+		GridBagConstraints gbc_gender = new GridBagConstraints();
+		gbc_gender.insets = new Insets(0, 0, 0, 5);
+		gbc_gender.fill = GridBagConstraints.HORIZONTAL;
+		gbc_gender.gridx = 2;
+		gbc_gender.gridy = 0;
+		panel_2.add(gender, gbc_gender);
+		
+		height = new JTextField();
+		height.setMaximumSize(new Dimension(2147483647, 30));
+		height.setMinimumSize(new Dimension(6, 30));
+		height.setPreferredSize(new Dimension(6, 30));
+		height.setColumns(10);
+		height.setBorder(new TitledBorder(lineBorder, "HEIGHT", borderAlign, titlePos, titleFont, titleColor));
+		GridBagConstraints gbc_height = new GridBagConstraints();
+		gbc_height.fill = GridBagConstraints.HORIZONTAL;
+		gbc_height.gridx = 3;
+		gbc_height.gridy = 0;
+		panel_2.add(height, gbc_height);
+		
+		JPanel panel_1 = new JPanel();
+		panel_1.setOpaque(false);
+		GridBagConstraints gbc_panel_1 = new GridBagConstraints();
+		gbc_panel_1.fill = GridBagConstraints.BOTH;
+		gbc_panel_1.gridx = 1;
+		gbc_panel_1.gridy = 0;
+		add(panel_1, gbc_panel_1);
+		GridBagLayout gbl_panel_1 = new GridBagLayout();
+		gbl_panel_1.columnWidths = new int[]{0, 0};
+		gbl_panel_1.rowHeights = new int[]{0, 0, 0, 0};
+		gbl_panel_1.columnWeights = new double[]{1.0, Double.MIN_VALUE};
+		gbl_panel_1.rowWeights = new double[]{1.0, 1.0, 1.0, Double.MIN_VALUE};
+		panel_1.setLayout(gbl_panel_1);
+		
+		JPanel panel_7 = new JPanel();
+		panel_7.setPreferredSize(new Dimension(10, 30));
+		panel_7.setMinimumSize(new Dimension(10, 30));
+		panel_7.setOpaque(false);
+		GridBagConstraints gbc_panel_7 = new GridBagConstraints();
+		gbc_panel_7.insets = new Insets(0, 0, 5, 0);
+		gbc_panel_7.fill = GridBagConstraints.BOTH;
+		gbc_panel_7.gridx = 0;
+		gbc_panel_7.gridy = 0;
+		panel_1.add(panel_7, gbc_panel_7);
+		GridBagLayout gbl_panel_7 = new GridBagLayout();
+		gbl_panel_7.columnWidths = new int[]{0, 0};
+		gbl_panel_7.rowHeights = new int[]{0, 0};
+		gbl_panel_7.columnWeights = new double[]{1.0, Double.MIN_VALUE};
+		gbl_panel_7.rowWeights = new double[]{1.0, Double.MIN_VALUE};
+		panel_7.setLayout(gbl_panel_7);
+		
+		player = new JTextField();
+		player.setMaximumSize(new Dimension(2147483647, 30));
+		player.setMinimumSize(new Dimension(6, 30));
+		player.setPreferredSize(new Dimension(6, 30));
+		player.setColumns(10);
+		player.setBorder(new TitledBorder(lineBorder, "PLAYER", borderAlign, titlePos, titleFont, titleColor));
+		GridBagConstraints gbc_player = new GridBagConstraints();
+		gbc_player.fill = GridBagConstraints.HORIZONTAL;
+		gbc_player.gridx = 0;
+		gbc_player.gridy = 0;
+		panel_7.add(player, gbc_player);
+		
+		JPanel panel_6 = new JPanel();
+		panel_6.setPreferredSize(new Dimension(10, 30));
+		panel_6.setMinimumSize(new Dimension(10, 30));
+		panel_6.setOpaque(false);
+		GridBagConstraints gbc_panel_6 = new GridBagConstraints();
+		gbc_panel_6.insets = new Insets(0, 0, 5, 0);
+		gbc_panel_6.fill = GridBagConstraints.BOTH;
+		gbc_panel_6.gridx = 0;
+		gbc_panel_6.gridy = 1;
+		panel_1.add(panel_6, gbc_panel_6);
+		GridBagLayout gbl_panel_6 = new GridBagLayout();
+		gbl_panel_6.columnWidths = new int[]{0, 0, 0, 0};
+		gbl_panel_6.rowHeights = new int[]{0, 0};
+		gbl_panel_6.columnWeights = new double[]{1.0, 1.0, 1.0, Double.MIN_VALUE};
+		gbl_panel_6.rowWeights = new double[]{1.0, Double.MIN_VALUE};
+		panel_6.setLayout(gbl_panel_6);
+		
+		race = new JTextField();
+		race.setMaximumSize(new Dimension(2147483647, 30));
+		race.setMinimumSize(new Dimension(6, 30));
+		race.setPreferredSize(new Dimension(6, 30));
+		race.setColumns(10);
+		race.setBorder(new TitledBorder(lineBorder, "RACE", borderAlign, titlePos, titleFont, titleColor));
+		GridBagConstraints gbc_race = new GridBagConstraints();
+		gbc_race.insets = new Insets(0, 0, 0, 5);
+		gbc_race.fill = GridBagConstraints.HORIZONTAL;
+		gbc_race.gridx = 0;
+		gbc_race.gridy = 0;
+		panel_6.add(race, gbc_race);
+		
+		alignment = new JTextField();
+		alignment.setMaximumSize(new Dimension(2147483647, 30));
+		alignment.setMinimumSize(new Dimension(6, 30));
+		alignment.setPreferredSize(new Dimension(6, 30));
+		alignment.setColumns(10);
+		alignment.setBorder(new TitledBorder(lineBorder, "ALIGNMENT", borderAlign, titlePos, titleFont, titleColor));
+		GridBagConstraints gbc_alignment = new GridBagConstraints();
+		gbc_alignment.insets = new Insets(0, 0, 0, 5);
+		gbc_alignment.fill = GridBagConstraints.HORIZONTAL;
+		gbc_alignment.gridx = 1;
+		gbc_alignment.gridy = 0;
+		panel_6.add(alignment, gbc_alignment);
+		
+		deity = new JTextField();
+		deity.setMaximumSize(new Dimension(2147483647, 30));
+		deity.setMinimumSize(new Dimension(6, 30));
+		deity.setPreferredSize(new Dimension(6, 30));
+		deity.setColumns(10);
+		deity.setBorder(new TitledBorder(lineBorder, "DEITY", borderAlign, titlePos, titleFont, titleColor));
+		GridBagConstraints gbc_deity = new GridBagConstraints();
+		gbc_deity.fill = GridBagConstraints.HORIZONTAL;
+		gbc_deity.gridx = 2;
+		gbc_deity.gridy = 0;
+		panel_6.add(deity, gbc_deity);
+		
+		JPanel panel_5 = new JPanel();
+		panel_5.setPreferredSize(new Dimension(10, 30));
+		panel_5.setMinimumSize(new Dimension(10, 30));
+		panel_5.setOpaque(false);
+		GridBagConstraints gbc_panel_5 = new GridBagConstraints();
+		gbc_panel_5.fill = GridBagConstraints.BOTH;
+		gbc_panel_5.gridx = 0;
+		gbc_panel_5.gridy = 2;
+		panel_1.add(panel_5, gbc_panel_5);
+		GridBagLayout gbl_panel_5 = new GridBagLayout();
+		gbl_panel_5.columnWidths = new int[]{0, 0, 0, 0, 0};
+		gbl_panel_5.rowHeights = new int[]{0, 0};
+		gbl_panel_5.columnWeights = new double[]{1.0, 1.0, 1.0, 1.0, Double.MIN_VALUE};
+		gbl_panel_5.rowWeights = new double[]{1.0, Double.MIN_VALUE};
+		panel_5.setLayout(gbl_panel_5);
+		
+		weight = new JTextField();
+		weight.setMaximumSize(new Dimension(2147483647, 30));
+		weight.setMinimumSize(new Dimension(6, 30));
+		weight.setPreferredSize(new Dimension(6, 30));
+		weight.setColumns(10);
+		weight.setBorder(new TitledBorder(lineBorder, "WEIGHT", borderAlign, titlePos, titleFont, titleColor));
+		GridBagConstraints gbc_weight = new GridBagConstraints();
+		gbc_weight.insets = new Insets(0, 0, 0, 5);
+		gbc_weight.fill = GridBagConstraints.HORIZONTAL;
+		gbc_weight.gridx = 0;
+		gbc_weight.gridy = 0;
+		panel_5.add(weight, gbc_weight);
+		
+		eyes = new JTextField();
+		eyes.setMaximumSize(new Dimension(2147483647, 30));
+		eyes.setMinimumSize(new Dimension(6, 30));
+		eyes.setPreferredSize(new Dimension(6, 30));
+		eyes.setColumns(10);
+		eyes.setBorder(new TitledBorder(lineBorder, "EYES", borderAlign, titlePos, titleFont, titleColor));
+		GridBagConstraints gbc_eyes = new GridBagConstraints();
+		gbc_eyes.insets = new Insets(0, 0, 0, 5);
+		gbc_eyes.fill = GridBagConstraints.HORIZONTAL;
+		gbc_eyes.gridx = 1;
+		gbc_eyes.gridy = 0;
+		panel_5.add(eyes, gbc_eyes);
+		
+		hair = new JTextField();
+		hair.setMaximumSize(new Dimension(2147483647, 30));
+		hair.setMinimumSize(new Dimension(6, 30));
+		hair.setPreferredSize(new Dimension(6, 30));
+		hair.setColumns(10);
+		hair.setBorder(new TitledBorder(lineBorder, "HAIR", borderAlign, titlePos, titleFont, titleColor));
+		GridBagConstraints gbc_hair = new GridBagConstraints();
+		gbc_hair.insets = new Insets(0, 0, 0, 5);
+		gbc_hair.fill = GridBagConstraints.HORIZONTAL;
+		gbc_hair.gridx = 2;
+		gbc_hair.gridy = 0;
+		panel_5.add(hair, gbc_hair);
+		
+		skin = new JTextField();
+		skin.setMaximumSize(new Dimension(2147483647, 30));
+		skin.setMinimumSize(new Dimension(6, 30));
+		skin.setPreferredSize(new Dimension(6, 30));
+		skin.setColumns(10);
+		skin.setBorder(new TitledBorder(lineBorder, "SKIN", borderAlign, titlePos, titleFont, titleColor));
+		GridBagConstraints gbc_skin = new GridBagConstraints();
+		gbc_skin.fill = GridBagConstraints.HORIZONTAL;
+		gbc_skin.gridx = 3;
+		gbc_skin.gridy = 0;
+		panel_5.add(skin, gbc_skin);
+	}
+	
+	public void setModel(DDCharacter model) {
+		this.model = model;
+	}
+
+	public void updateModel() {
+		this.name.setText(this.model.getName());
+		this.player.setText(this.model.getPlayer());
+		this.level.setText(this.model.getClassAndLevelString());
+		this.race.setText(this.model.getRace());
+		this.alignment.setText(StringHelper.toString(this.model.getAlignment()));
+		this.deity.setText(this.model.getDeity());
+		this.size.setText(StringHelper.toString(this.model.getSize()));
+		this.age.setText(StringHelper.toString(this.model.getAge(), -1));
+		this.gender.setText(StringHelper.toString(this.model.getGender()));
+		this.height.setText(this.model.getHeight());
+		this.weight.setText(StringHelper.toString(this.model.getWeight(), -1));
+		this.eyes.setText(this.model.getEyes());
+		this.hair.setText(this.model.getHair());
+		this.skin.setText(this.model.getSkin());
+	}
+
+}

+ 172 - 0
src/org/leumasjaffe/charsheet/view/summary/HealthLine.java

@@ -0,0 +1,172 @@
+package org.leumasjaffe.charsheet.view.summary;
+
+import javax.swing.JPanel;
+import java.awt.GridBagLayout;
+import javax.swing.JLabel;
+import java.awt.GridBagConstraints;
+import java.awt.Color;
+import javax.swing.border.LineBorder;
+
+import java.awt.Font;
+import javax.swing.SwingConstants;
+import java.awt.Dimension;
+import javax.swing.JTextField;
+import java.awt.Insets;
+
+import org.leumasjaffe.charsheet.entity.HitPoints;
+import org.leumasjaffe.charsheet.entity.viewable.IntValue;
+import org.leumasjaffe.charsheet.observer.ObservableController;
+import org.leumasjaffe.charsheet.observer.ObservableListener;
+import org.leumasjaffe.charsheet.observer.helper.IntValueHelper;
+import org.leumasjaffe.charsheet.view.StringHelper;
+import org.leumasjaffe.graphics.NumberTextField;
+
+import lombok.AccessLevel;
+import lombok.experimental.FieldDefaults;
+
+import java.awt.Component;
+import javax.swing.Box;
+
+@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
+public class HealthLine extends JPanel {
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = 1L;
+	ObservableListener<IntValue> ttlObserver;
+	ObservableListener<IntValue> currObserver;
+	ObservableListener<IntValue> nlObserver;
+	ObservableListener<IntValue> tempObserver;
+
+	public HealthLine() {
+		setOpaque(false);
+		GridBagLayout gridBagLayout = new GridBagLayout();
+		gridBagLayout.columnWidths = new int[]{0, 0, 0, 0, 0, 0, 0, 0, 0};
+		gridBagLayout.rowHeights = new int[]{0, 0};
+		gridBagLayout.columnWeights = new double[]{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, Double.MIN_VALUE};
+		gridBagLayout.rowWeights = new double[]{0.0, Double.MIN_VALUE};
+		setLayout(gridBagLayout);
+		
+		JLabel label = new JLabel("HP");
+		label.setToolTipText("Hit Points");
+		label.setPreferredSize(new Dimension(50, 25));
+		label.setOpaque(true);
+		label.setMinimumSize(new Dimension(50, 25));
+		label.setMaximumSize(new Dimension(50, 25));
+		label.setHorizontalAlignment(SwingConstants.CENTER);
+		label.setForeground(Color.WHITE);
+		label.setFont(new Font("Tahoma", Font.BOLD, 18));
+		label.setBorder(new LineBorder(Color.WHITE));
+		label.setBackground(Color.BLACK);
+		GridBagConstraints gbc_label = new GridBagConstraints();
+		gbc_label.insets = new Insets(0, 0, 0, 5);
+		gbc_label.anchor = GridBagConstraints.EAST;
+		gbc_label.gridx = 0;
+		gbc_label.gridy = 0;
+		add(label, gbc_label);
+		
+		NumberTextField currentHealth = new NumberTextField();
+		currentHealth.setToolTipText("Current Health");
+		currentHealth.setPreferredSize(new Dimension(30, 20));
+		currentHealth.setMinimumSize(new Dimension(30, 20));
+		currentHealth.setMaximumSize(new Dimension(30, 20));
+		currentHealth.setHorizontalAlignment(SwingConstants.CENTER);
+		currentHealth.setColumns(3);
+		currentHealth.setBorder(new LineBorder(Color.BLACK));
+		GridBagConstraints gbc_currentHealth = new GridBagConstraints();
+		gbc_currentHealth.insets = new Insets(0, 0, 0, 5);
+		gbc_currentHealth.fill = GridBagConstraints.HORIZONTAL;
+		gbc_currentHealth.gridx = 1;
+		gbc_currentHealth.gridy = 0;
+		add(currentHealth, gbc_currentHealth);
+		
+		JLabel label_1 = new JLabel("/");
+		GridBagConstraints gbc_label_1 = new GridBagConstraints();
+		gbc_label_1.insets = new Insets(0, 0, 0, 5);
+		gbc_label_1.anchor = GridBagConstraints.EAST;
+		gbc_label_1.gridx = 2;
+		gbc_label_1.gridy = 0;
+		add(label_1, gbc_label_1);
+		
+		JTextField maxHealth = new JTextField();
+		maxHealth.setToolTipText("Maximum Health");
+		maxHealth.setPreferredSize(new Dimension(30, 20));
+		maxHealth.setMinimumSize(new Dimension(30, 20));
+		maxHealth.setMaximumSize(new Dimension(30, 20));
+		maxHealth.setHorizontalAlignment(SwingConstants.CENTER);
+		maxHealth.setEditable(false);
+		maxHealth.setColumns(3);
+		maxHealth.setBorder(new LineBorder(Color.BLACK));
+		GridBagConstraints gbc_maxHealth = new GridBagConstraints();
+		gbc_maxHealth.insets = new Insets(0, 0, 0, 5);
+		gbc_maxHealth.fill = GridBagConstraints.HORIZONTAL;
+		gbc_maxHealth.gridx = 3;
+		gbc_maxHealth.gridy = 0;
+		add(maxHealth, gbc_maxHealth);
+		
+		JLabel label_2 = new JLabel("+");
+		GridBagConstraints gbc_label_2 = new GridBagConstraints();
+		gbc_label_2.insets = new Insets(0, 0, 0, 5);
+		gbc_label_2.anchor = GridBagConstraints.EAST;
+		gbc_label_2.gridx = 4;
+		gbc_label_2.gridy = 0;
+		add(label_2, gbc_label_2);
+		
+		NumberTextField temporary = new NumberTextField();
+		temporary.setToolTipText("Temporary Health");
+		temporary.setPreferredSize(new Dimension(30, 20));
+		temporary.setMinimumSize(new Dimension(30, 20));
+		temporary.setMaximumSize(new Dimension(30, 20));
+		temporary.setHorizontalAlignment(SwingConstants.CENTER);
+		temporary.setColumns(3);
+		temporary.setBorder(new LineBorder(Color.BLACK));
+		GridBagConstraints gbc_temporary = new GridBagConstraints();
+		gbc_temporary.insets = new Insets(0, 0, 0, 5);
+		gbc_temporary.fill = GridBagConstraints.HORIZONTAL;
+		gbc_temporary.gridx = 5;
+		gbc_temporary.gridy = 0;
+		add(temporary, gbc_temporary);
+		
+		Component horizontalStrut = Box.createHorizontalStrut(20);
+		GridBagConstraints gbc_horizontalStrut = new GridBagConstraints();
+		gbc_horizontalStrut.insets = new Insets(0, 0, 0, 5);
+		gbc_horizontalStrut.gridx = 6;
+		gbc_horizontalStrut.gridy = 0;
+		add(horizontalStrut, gbc_horizontalStrut);
+		
+		NumberTextField nonlethal = new NumberTextField();
+		nonlethal.setToolTipText("Subdual Damage");
+		nonlethal.setPreferredSize(new Dimension(30, 20));
+		nonlethal.setMinimumSize(new Dimension(30, 20));
+		nonlethal.setMaximumSize(new Dimension(30, 20));
+		nonlethal.setHorizontalAlignment(SwingConstants.CENTER);
+		nonlethal.setColumns(3);
+		nonlethal.setBorder(new LineBorder(Color.BLACK));
+		GridBagConstraints gbc_nonlethal = new GridBagConstraints();
+		gbc_nonlethal.fill = GridBagConstraints.HORIZONTAL;
+		gbc_nonlethal.gridx = 7;
+		gbc_nonlethal.gridy = 0;
+		add(nonlethal, gbc_nonlethal);
+		
+		ttlObserver = new ObservableController<>(maxHealth,
+				new IntValueHelper(),
+				(v) -> StringHelper.toString(v.value(), 0));
+		currObserver = new ObservableController<>(currentHealth,
+				new IntValueHelper(),
+				(v) -> StringHelper.toString(v.value()));
+		tempObserver = new ObservableController<>(temporary,
+				new IntValueHelper(),
+				(v) -> StringHelper.toString(v.value(), 0));
+		nlObserver = new ObservableController<>(nonlethal,
+				new IntValueHelper(),
+				(v) -> StringHelper.toString(v.value(), 0));
+	}
+
+	public void setModel(HitPoints health) {
+		ttlObserver.setObserved(health.getTotal());
+		currObserver.setObserved(health.getCurrent());
+		tempObserver.setObserved(health.getTemp());
+		nlObserver.setObserved(health.getNonlethal());
+	}
+
+}

+ 142 - 0
src/org/leumasjaffe/charsheet/view/summary/InitiativeLine.java

@@ -0,0 +1,142 @@
+package org.leumasjaffe.charsheet.view.summary;
+
+import javax.swing.JPanel;
+import java.awt.GridBagLayout;
+import javax.swing.JLabel;
+import java.awt.GridBagConstraints;
+
+import static org.leumasjaffe.charsheet.entity.AbilityScores.modifer;
+
+import java.awt.Color;
+import javax.swing.border.LineBorder;
+
+import org.leumasjaffe.charsheet.entity.AbilityScores;
+import org.leumasjaffe.charsheet.entity.DDCharacter;
+import org.leumasjaffe.charsheet.entity.viewable.IntValue;
+import org.leumasjaffe.charsheet.observer.ObservableListener;
+import org.leumasjaffe.charsheet.observer.helper.AbilModStringify;
+import org.leumasjaffe.charsheet.view.StringHelper;
+
+import lombok.AccessLevel;
+import lombok.experimental.FieldDefaults;
+import lombok.experimental.NonFinal;
+
+import java.awt.Font;
+import javax.swing.SwingConstants;
+import java.awt.Dimension;
+import javax.swing.JTextField;
+import java.awt.Insets;
+
+@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
+public class InitiativeLine extends JPanel {
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = 1L;
+	JTextField misc;
+	@NonFinal AbilityScores model;
+	ObservableListener<IntValue> ttlObserver;
+	ObservableListener<IntValue> dexObserver;
+
+	public InitiativeLine() {
+		setOpaque(false);
+		GridBagLayout gridBagLayout = new GridBagLayout();
+		gridBagLayout.columnWidths = new int[]{0, 0, 0, 0, 0, 0, 0};
+		gridBagLayout.rowHeights = new int[]{0, 0};
+		gridBagLayout.columnWeights = new double[]{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, Double.MIN_VALUE};
+		gridBagLayout.rowWeights = new double[]{0.0, Double.MIN_VALUE};
+		setLayout(gridBagLayout);
+		
+		JLabel lblInitiative = new JLabel("INITIATIVE");
+		lblInitiative.setToolTipText("");
+		lblInitiative.setPreferredSize(new Dimension(120, 25));
+		lblInitiative.setOpaque(true);
+		lblInitiative.setMinimumSize(new Dimension(50, 25));
+		lblInitiative.setMaximumSize(new Dimension(50, 25));
+		lblInitiative.setHorizontalAlignment(SwingConstants.CENTER);
+		lblInitiative.setForeground(Color.WHITE);
+		lblInitiative.setFont(new Font("Tahoma", Font.BOLD, 18));
+		lblInitiative.setBorder(new LineBorder(Color.WHITE));
+		lblInitiative.setBackground(Color.BLACK);
+		GridBagConstraints gbc_lblInitiative = new GridBagConstraints();
+		gbc_lblInitiative.insets = new Insets(0, 0, 0, 5);
+		gbc_lblInitiative.anchor = GridBagConstraints.EAST;
+		gbc_lblInitiative.gridx = 0;
+		gbc_lblInitiative.gridy = 0;
+		add(lblInitiative, gbc_lblInitiative);
+		
+		JTextField total = new JTextField();
+		total.setToolTipText("Total Initiative");
+		total.setPreferredSize(new Dimension(30, 20));
+		total.setMinimumSize(new Dimension(30, 20));
+		total.setMaximumSize(new Dimension(30, 20));
+		total.setHorizontalAlignment(SwingConstants.CENTER);
+		total.setEditable(false);
+		total.setColumns(3);
+		total.setBorder(new LineBorder(Color.BLACK));
+		GridBagConstraints gbc_total = new GridBagConstraints();
+		gbc_total.insets = new Insets(0, 0, 0, 5);
+		gbc_total.fill = GridBagConstraints.HORIZONTAL;
+		gbc_total.gridx = 1;
+		gbc_total.gridy = 0;
+		add(total, gbc_total);
+		
+		JLabel label = new JLabel("=");
+		GridBagConstraints gbc_label = new GridBagConstraints();
+		gbc_label.insets = new Insets(0, 0, 0, 5);
+		gbc_label.anchor = GridBagConstraints.EAST;
+		gbc_label.gridx = 2;
+		gbc_label.gridy = 0;
+		add(label, gbc_label);
+		
+		JTextField dex = new JTextField();
+		dex.setToolTipText("Dexterity Modifier");
+		dex.setPreferredSize(new Dimension(30, 20));
+		dex.setMinimumSize(new Dimension(30, 20));
+		dex.setMaximumSize(new Dimension(30, 20));
+		dex.setHorizontalAlignment(SwingConstants.CENTER);
+		dex.setEditable(false);
+		dex.setColumns(3);
+		dex.setBorder(new LineBorder(Color.BLACK));
+		GridBagConstraints gbc_dex = new GridBagConstraints();
+		gbc_dex.insets = new Insets(0, 0, 0, 5);
+		gbc_dex.fill = GridBagConstraints.HORIZONTAL;
+		gbc_dex.gridx = 3;
+		gbc_dex.gridy = 0;
+		add(dex, gbc_dex);
+		
+		JLabel label_1 = new JLabel("+");
+		GridBagConstraints gbc_label_1 = new GridBagConstraints();
+		gbc_label_1.insets = new Insets(0, 0, 0, 5);
+		gbc_label_1.anchor = GridBagConstraints.EAST;
+		gbc_label_1.gridx = 4;
+		gbc_label_1.gridy = 0;
+		add(label_1, gbc_label_1);
+		
+		misc = new JTextField();
+		misc.setToolTipText("Miscellaneous Modifier");
+		misc.setPreferredSize(new Dimension(30, 20));
+		misc.setMinimumSize(new Dimension(30, 20));
+		misc.setMaximumSize(new Dimension(30, 20));
+		misc.setHorizontalAlignment(SwingConstants.CENTER);
+		misc.setEditable(false);
+		misc.setColumns(3);
+		misc.setBorder(new LineBorder(Color.BLACK));
+		GridBagConstraints gbc_misc = new GridBagConstraints();
+		gbc_misc.fill = GridBagConstraints.HORIZONTAL;
+		gbc_misc.gridx = 5;
+		gbc_misc.gridy = 0;
+		add(misc, gbc_misc);
+		
+		ttlObserver = new ObservableListener<>(total, 
+				new AbilModStringify());
+		dexObserver = new ObservableListener<>(dex, 
+				new AbilModStringify());
+	}
+
+	public void setModel(DDCharacter model) {
+		ttlObserver.setObserved(model.getAbilities().getBase().getDex());
+		dexObserver.setObserved(model.getAbilities().getBase().getDex());
+	}
+
+}

+ 251 - 0
src/org/leumasjaffe/charsheet/view/summary/ResistanceLine.java

@@ -0,0 +1,251 @@
+package org.leumasjaffe.charsheet.view.summary;
+
+import javax.swing.JPanel;
+import java.awt.GridBagLayout;
+import javax.swing.JLabel;
+import java.awt.GridBagConstraints;
+import java.awt.Color;
+import javax.swing.border.LineBorder;
+
+import org.leumasjaffe.charsheet.entity.AbilityScores;
+import org.leumasjaffe.charsheet.entity.DDCharacter;
+import org.leumasjaffe.charsheet.entity.viewable.IntValue;
+import org.leumasjaffe.charsheet.observer.ObservableListener;
+import org.leumasjaffe.charsheet.observer.helper.AbilModStringify;
+import org.leumasjaffe.charsheet.view.StringHelper;
+import org.leumasjaffe.graphics.NumberTextField;
+
+import lombok.AccessLevel;
+import lombok.experimental.FieldDefaults;
+import lombok.experimental.NonFinal;
+
+import java.awt.Font;
+import javax.swing.SwingConstants;
+import java.awt.Dimension;
+import javax.swing.JTextField;
+import java.awt.Insets;
+import java.util.function.Function;
+
+import javax.swing.JFormattedTextField;
+
+@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
+public class ResistanceLine extends JPanel {
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = 1L;
+	Function<DDCharacter, Integer> save;
+	Function<AbilityScores.Scores, IntValue> access;
+	@NonFinal DDCharacter model;
+	
+	JTextField total;
+	JTextField ability;
+	NumberTextField temp;
+	JFormattedTextField baseSave;
+	JFormattedTextField magic;
+	JFormattedTextField misc;
+	private ObservableListener<IntValue> abilObserver;
+	
+	public ResistanceLine(final String name, Function<DDCharacter, Integer> save, 
+			Function<AbilityScores.Scores, IntValue> func) {
+		this.save = save;
+		this.access = func;
+		
+		setMinimumSize(new Dimension(400, 25));
+		setMaximumSize(new Dimension(400, 25));
+		setPreferredSize(new Dimension(400, 25));
+		setOpaque(false);
+		GridBagLayout gridBagLayout = new GridBagLayout();
+		gridBagLayout.columnWidths = new int[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+		gridBagLayout.rowHeights = new int[]{0, 0};
+		gridBagLayout.columnWeights = new double[]{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, Double.MIN_VALUE};
+		gridBagLayout.rowWeights = new double[]{0.0, Double.MIN_VALUE};
+		setLayout(gridBagLayout);
+		
+		JLabel label = new JLabel(name.toUpperCase());
+		label.setPreferredSize(new Dimension(120, 25));
+		label.setOpaque(true);
+		label.setMinimumSize(new Dimension(120, 25));
+		label.setMaximumSize(new Dimension(120, 25));
+		label.setHorizontalAlignment(SwingConstants.CENTER);
+		label.setForeground(Color.WHITE);
+		label.setFont(new Font("Tahoma", Font.BOLD, 18));
+		label.setBorder(new LineBorder(Color.WHITE));
+		label.setBackground(Color.BLACK);
+		GridBagConstraints gbc_label = new GridBagConstraints();
+		gbc_label.fill = GridBagConstraints.HORIZONTAL;
+		gbc_label.insets = new Insets(0, 0, 0, 5);
+		gbc_label.gridx = 0;
+		gbc_label.gridy = 0;
+		add(label, gbc_label);
+		
+		total = new JTextField();
+		total.setToolTipText("Saving Throw");
+		total.setPreferredSize(new Dimension(30, 20));
+		total.setMinimumSize(new Dimension(30, 20));
+		total.setMaximumSize(new Dimension(30, 20));
+		total.setHorizontalAlignment(SwingConstants.CENTER);
+		total.setEditable(false);
+		total.setColumns(3);
+		total.setBorder(new LineBorder(Color.BLACK));
+		GridBagConstraints gbc_total = new GridBagConstraints();
+		gbc_total.insets = new Insets(0, 0, 0, 5);
+		gbc_total.fill = GridBagConstraints.HORIZONTAL;
+		gbc_total.gridx = 1;
+		gbc_total.gridy = 0;
+		add(total, gbc_total);
+		
+		JLabel label_1 = new JLabel("=");
+		GridBagConstraints gbc_label_1 = new GridBagConstraints();
+		gbc_label_1.anchor = GridBagConstraints.EAST;
+		gbc_label_1.insets = new Insets(0, 0, 0, 5);
+		gbc_label_1.gridx = 2;
+		gbc_label_1.gridy = 0;
+		add(label_1, gbc_label_1);
+		
+		baseSave = new NumberTextField();
+		baseSave.setEditable(false);
+		baseSave.setToolTipText("Base Save");
+		baseSave.setPreferredSize(new Dimension(30, 20));
+		baseSave.setMinimumSize(new Dimension(30, 20));
+		baseSave.setMaximumSize(new Dimension(30, 20));
+		baseSave.setHorizontalAlignment(SwingConstants.CENTER);
+		baseSave.setColumns(3);
+		baseSave.setBorder(new LineBorder(Color.BLACK));
+		GridBagConstraints gbc_baseSave = new GridBagConstraints();
+		gbc_baseSave.insets = new Insets(0, 0, 0, 5);
+		gbc_baseSave.fill = GridBagConstraints.HORIZONTAL;
+		gbc_baseSave.gridx = 3;
+		gbc_baseSave.gridy = 0;
+		add(baseSave, gbc_baseSave);
+		
+		JLabel label_2 = new JLabel("+");
+		GridBagConstraints gbc_label_2 = new GridBagConstraints();
+		gbc_label_2.insets = new Insets(0, 0, 0, 5);
+		gbc_label_2.anchor = GridBagConstraints.EAST;
+		gbc_label_2.gridx = 4;
+		gbc_label_2.gridy = 0;
+		add(label_2, gbc_label_2);
+		
+		ability = new JTextField();
+		ability.setToolTipText("Ability Modifier");
+		ability.setPreferredSize(new Dimension(30, 20));
+		ability.setMinimumSize(new Dimension(30, 20));
+		ability.setMaximumSize(new Dimension(30, 20));
+		ability.setHorizontalAlignment(SwingConstants.CENTER);
+		ability.setEditable(false);
+		ability.setColumns(3);
+		ability.setBorder(new LineBorder(Color.BLACK));
+		GridBagConstraints gbc_ability = new GridBagConstraints();
+		gbc_ability.insets = new Insets(0, 0, 0, 5);
+		gbc_ability.fill = GridBagConstraints.HORIZONTAL;
+		gbc_ability.gridx = 5;
+		gbc_ability.gridy = 0;
+		add(ability, gbc_ability);
+		
+		JLabel label_3 = new JLabel("+");
+		GridBagConstraints gbc_label_3 = new GridBagConstraints();
+		gbc_label_3.anchor = GridBagConstraints.EAST;
+		gbc_label_3.insets = new Insets(0, 0, 0, 5);
+		gbc_label_3.gridx = 6;
+		gbc_label_3.gridy = 0;
+		add(label_3, gbc_label_3);
+		
+		magic = new NumberTextField();
+		magic.setToolTipText("Magic Modifier");
+		magic.setPreferredSize(new Dimension(30, 20));
+		magic.setMinimumSize(new Dimension(30, 20));
+		magic.setMaximumSize(new Dimension(30, 20));
+		magic.setHorizontalAlignment(SwingConstants.CENTER);
+		magic.setColumns(3);
+		magic.setBorder(new LineBorder(Color.BLACK));
+		GridBagConstraints gbc_magic_1 = new GridBagConstraints();
+		gbc_magic_1.insets = new Insets(0, 0, 0, 5);
+		gbc_magic_1.fill = GridBagConstraints.HORIZONTAL;
+		gbc_magic_1.gridx = 7;
+		gbc_magic_1.gridy = 0;
+		add(magic, gbc_magic_1);
+		
+		JLabel label_4 = new JLabel("+");
+		GridBagConstraints gbc_label_4 = new GridBagConstraints();
+		gbc_label_4.anchor = GridBagConstraints.EAST;
+		gbc_label_4.insets = new Insets(0, 0, 0, 5);
+		gbc_label_4.gridx = 8;
+		gbc_label_4.gridy = 0;
+		add(label_4, gbc_label_4);
+		
+		misc = new NumberTextField();
+		misc.setToolTipText("Miscellaneous Modifier");
+		misc.setPreferredSize(new Dimension(30, 20));
+		misc.setMinimumSize(new Dimension(30, 20));
+		misc.setMaximumSize(new Dimension(30, 20));
+		misc.setHorizontalAlignment(SwingConstants.CENTER);
+		misc.setColumns(3);
+		misc.setBorder(new LineBorder(Color.BLACK));
+		GridBagConstraints gbc_misc_1 = new GridBagConstraints();
+		gbc_misc_1.insets = new Insets(0, 0, 0, 5);
+		gbc_misc_1.fill = GridBagConstraints.HORIZONTAL;
+		gbc_misc_1.gridx = 9;
+		gbc_misc_1.gridy = 0;
+		add(misc, gbc_misc_1);
+		
+		JLabel label_5 = new JLabel("+");
+		GridBagConstraints gbc_label_5 = new GridBagConstraints();
+		gbc_label_5.insets = new Insets(0, 0, 0, 5);
+		gbc_label_5.anchor = GridBagConstraints.EAST;
+		gbc_label_5.gridx = 10;
+		gbc_label_5.gridy = 0;
+		add(label_5, gbc_label_5);
+		
+		JPanel panel = new JPanel();
+		panel.setBackground(Color.LIGHT_GRAY);
+		panel.setPreferredSize(new Dimension(45, 25));
+		GridBagConstraints gbc_panel = new GridBagConstraints();
+		gbc_panel.fill = GridBagConstraints.BOTH;
+		gbc_panel.gridx = 11;
+		gbc_panel.gridy = 0;
+		add(panel, gbc_panel);
+		GridBagLayout gbl_panel = new GridBagLayout();
+		gbl_panel.columnWidths = new int[]{0, 0};
+		gbl_panel.rowHeights = new int[]{0, 0};
+		gbl_panel.columnWeights = new double[]{0.0, Double.MIN_VALUE};
+		gbl_panel.rowWeights = new double[]{0.0, Double.MIN_VALUE};
+		panel.setLayout(gbl_panel);
+		
+		temp = new NumberTextField();
+		temp.setToolTipText("Temporary Modifier");
+		temp.setPreferredSize(new Dimension(30, 20));
+		temp.setMinimumSize(new Dimension(30, 20));
+		temp.setMaximumSize(new Dimension(30, 20));
+		temp.setHorizontalAlignment(SwingConstants.CENTER);
+		temp.setColumns(3);
+		temp.setBorder(new LineBorder(Color.BLACK));
+		GridBagConstraints gbc_temp = new GridBagConstraints();
+		gbc_temp.insets = new Insets(3, 5, 0, 5);
+		gbc_temp.gridx = 0;
+		gbc_temp.gridy = 0;
+		panel.add(temp, gbc_temp);
+		
+		abilObserver = new ObservableListener<>(ability,
+				new AbilModStringify());
+	}
+	
+	public void setModel(DDCharacter model) {
+		this.model = model;
+		abilObserver.setObserved(access.apply(model.getAbilities().getBase()));
+	}
+	
+	public void updateModel() {
+		final int save = this.save.apply(this.model);
+		final int abil = AbilityScores.modifer(this.access.apply(this.model.getAbilities().getBase()).value());
+		final int magic = 0;
+		final int misc = 0;
+		final int temp = 0;
+		this.total.setText(StringHelper.toString(save + abil + magic + misc + temp));
+		this.baseSave.setText(StringHelper.toString(save));
+		this.magic.setText(StringHelper.toString(magic));
+		this.misc.setText(StringHelper.toString(misc));
+		this.temp.setText(StringHelper.toString(temp, 0));
+	}
+
+}

+ 68 - 0
src/org/leumasjaffe/charsheet/view/summary/ResistancePanel.java

@@ -0,0 +1,68 @@
+package org.leumasjaffe.charsheet.view.summary;
+
+import javax.swing.JPanel;
+
+import org.leumasjaffe.charsheet.entity.AbilityScores;
+import org.leumasjaffe.charsheet.entity.DDCharacter;
+
+import java.awt.GridBagLayout;
+import java.awt.GridBagConstraints;
+import java.awt.Dimension;
+
+public class ResistancePanel extends JPanel {
+
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = 1L;
+	private ResistanceLine fortitude;
+	private ResistanceLine reflex;
+	private ResistanceLine will;
+
+	public ResistancePanel() {
+		setMaximumSize(new Dimension(400, 75));
+		setMinimumSize(new Dimension(400, 74));
+		setPreferredSize(new Dimension(400, 75));
+		setOpaque(false);
+		GridBagLayout gridBagLayout = new GridBagLayout();
+		gridBagLayout.columnWidths = new int[]{0, 0};
+		gridBagLayout.rowHeights = new int[]{0, 0, 0, 0};
+		gridBagLayout.columnWeights = new double[]{0.0, Double.MIN_VALUE};
+		gridBagLayout.rowWeights = new double[]{0.0, 0.0, 0.0, Double.MIN_VALUE};
+		setLayout(gridBagLayout);
+		
+		fortitude = new ResistanceLine("FORTITUDE", DDCharacter::getFortSave, AbilityScores.Scores::getCon);
+		GridBagConstraints gbc_fortitude = new GridBagConstraints();
+		gbc_fortitude.fill = GridBagConstraints.BOTH;
+		gbc_fortitude.gridx = 0;
+		gbc_fortitude.gridy = 0;
+		add(fortitude, gbc_fortitude);
+		
+		reflex = new ResistanceLine("REFLEX", DDCharacter::getRefSave, AbilityScores.Scores::getDex);
+		GridBagConstraints gbc_reflex = new GridBagConstraints();
+		gbc_reflex.fill = GridBagConstraints.BOTH;
+		gbc_reflex.gridx = 0;
+		gbc_reflex.gridy = 1;
+		add(reflex, gbc_reflex);
+		
+		will = new ResistanceLine("WILL", DDCharacter::getWillSave, AbilityScores.Scores::getWis);
+		GridBagConstraints gbc_will = new GridBagConstraints();
+		gbc_will.fill = GridBagConstraints.BOTH;
+		gbc_will.gridx = 0;
+		gbc_will.gridy = 2;
+		add(will, gbc_will);
+	}
+
+	public void setModel(DDCharacter model) {
+		this.fortitude.setModel(model);
+		this.reflex.setModel(model);
+		this.will.setModel(model);
+	}
+
+	public void updateModel() {
+		this.fortitude.updateModel();
+		this.reflex.updateModel();
+		this.will.updateModel();
+	}
+
+}

+ 64 - 0
src/org/leumasjaffe/charsheet/view/summary/SpellResistanceLine.java

@@ -0,0 +1,64 @@
+package org.leumasjaffe.charsheet.view.summary;
+
+import javax.swing.JPanel;
+import java.awt.GridBagLayout;
+import javax.swing.JLabel;
+import java.awt.GridBagConstraints;
+import java.awt.Color;
+import javax.swing.border.LineBorder;
+import java.awt.Font;
+import javax.swing.SwingConstants;
+import java.awt.Dimension;
+import org.leumasjaffe.graphics.NumberTextField;
+import java.awt.Insets;
+
+public class SpellResistanceLine extends JPanel {
+
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = 1L;
+
+	public SpellResistanceLine() {
+		setOpaque(false);
+		GridBagLayout gridBagLayout = new GridBagLayout();
+		gridBagLayout.columnWidths = new int[]{0, 0, 0};
+		gridBagLayout.rowHeights = new int[]{0, 0};
+		gridBagLayout.columnWeights = new double[]{0.0, 0.0, Double.MIN_VALUE};
+		gridBagLayout.rowWeights = new double[]{0.0, Double.MIN_VALUE};
+		setLayout(gridBagLayout);
+		
+		JLabel lblSpellResistence = new JLabel("SR");
+		lblSpellResistence.setToolTipText("Spell Resistance");
+		lblSpellResistence.setPreferredSize(new Dimension(50, 25));
+		lblSpellResistence.setOpaque(true);
+		lblSpellResistence.setMinimumSize(new Dimension(50, 25));
+		lblSpellResistence.setMaximumSize(new Dimension(50, 25));
+		lblSpellResistence.setHorizontalAlignment(SwingConstants.CENTER);
+		lblSpellResistence.setForeground(Color.WHITE);
+		lblSpellResistence.setFont(new Font("Tahoma", Font.BOLD, 18));
+		lblSpellResistence.setBorder(new LineBorder(Color.WHITE));
+		lblSpellResistence.setBackground(Color.BLACK);
+		GridBagConstraints gbc_lblSpellResistence = new GridBagConstraints();
+		gbc_lblSpellResistence.insets = new Insets(0, 0, 0, 5);
+		gbc_lblSpellResistence.anchor = GridBagConstraints.EAST;
+		gbc_lblSpellResistence.gridx = 0;
+		gbc_lblSpellResistence.gridy = 0;
+		add(lblSpellResistence, gbc_lblSpellResistence);
+		
+		NumberTextField numberTextField = new NumberTextField();
+		numberTextField.setToolTipText("");
+		numberTextField.setPreferredSize(new Dimension(30, 20));
+		numberTextField.setMinimumSize(new Dimension(30, 20));
+		numberTextField.setMaximumSize(new Dimension(30, 20));
+		numberTextField.setHorizontalAlignment(SwingConstants.CENTER);
+		numberTextField.setColumns(3);
+		numberTextField.setBorder(new LineBorder(Color.BLACK));
+		GridBagConstraints gbc_numberTextField = new GridBagConstraints();
+		gbc_numberTextField.fill = GridBagConstraints.HORIZONTAL;
+		gbc_numberTextField.gridx = 1;
+		gbc_numberTextField.gridy = 0;
+		add(numberTextField, gbc_numberTextField);
+		// TODO Auto-generated constructor stub
+	}
+}

+ 83 - 0
src/org/leumasjaffe/event/AnyActionDocumentListener.java

@@ -0,0 +1,83 @@
+package org.leumasjaffe.event;
+
+import java.beans.PropertyChangeEvent;
+import java.util.Objects;
+import java.util.function.Consumer;
+
+import javax.swing.SwingUtilities;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import javax.swing.text.Document;
+import javax.swing.text.JTextComponent;
+
+@FunctionalInterface
+public interface AnyActionDocumentListener extends DocumentListener {
+
+	public void update(DocumentEvent e);
+	
+	public default void insertUpdate(DocumentEvent e) { this.update(e); }
+    public default void removeUpdate(DocumentEvent e) { this.update(e); }
+    public default void changedUpdate(DocumentEvent e) { this.update(e); }
+    
+    public static void skipEmpty(final JTextComponent text, 
+    		final Consumer<ChangeEvent> in) {
+    	addChangeListener(text, e -> {
+    		if (text.getText().trim().isEmpty()) { return; }
+    		in.accept(e);
+    	});
+    }
+    
+    public static void emptyOrText(final JTextComponent text, 
+    		final Consumer<ChangeEvent> empty,
+    		final Consumer<ChangeEvent> in) {
+    	addChangeListener(text, e -> {
+    		if (text.getText().trim().isEmpty()) { empty.accept(e); }
+    		else { in.accept(e); }
+    	});
+    }
+    
+    /**
+	 * Installs a listener to receive notification when the text of any
+	 * {@code JTextComponent} is changed. Internally, it installs a
+	 * {@link DocumentListener} on the text component's {@link Document},
+	 * and a {@link PropertyChangeListener} on the text component to detect
+	 * if the {@code Document} itself is replaced.
+	 * 
+	 * @param text any text component, such as a {@link JTextField}
+	 *        or {@link JTextArea}
+	 * @param changeListener a listener to receieve {@link ChangeEvent}s
+	 *        when the text is changed; the source object for the events
+	 *        will be the text component
+	 * @throws NullPointerException if either parameter is null
+	 */
+	public static void addChangeListener(final JTextComponent text, 
+			final ChangeListener changeListener) {
+	    Objects.requireNonNull(text);
+	    Objects.requireNonNull(changeListener);
+	    final DocumentListener dl = new AnyActionDocumentListener() {
+	        private int lastChange = 0, lastNotifiedChange = 0;
+
+	        @Override
+	        public void update(DocumentEvent e) {
+	            lastChange++;
+	            SwingUtilities.invokeLater(() -> {
+	                if (lastNotifiedChange != lastChange) {
+	                    lastNotifiedChange = lastChange;
+	                    changeListener.stateChanged(new ChangeEvent(text));
+	                }
+	            });
+	        }
+	    };
+	    text.addPropertyChangeListener("document", (PropertyChangeEvent e) -> {
+	        Document d1 = (Document)e.getOldValue();
+	        Document d2 = (Document)e.getNewValue();
+	        if (d1 != null) d1.removeDocumentListener(dl);
+	        if (d2 != null) d2.addDocumentListener(dl);
+	        dl.changedUpdate(null);
+	    });
+	    Document d = text.getDocument();
+	    if (d != null) d.addDocumentListener(dl);
+	}
+}

+ 46 - 0
src/org/leumasjaffe/graphics/NumberTextField.java

@@ -0,0 +1,46 @@
+package org.leumasjaffe.graphics;
+
+import java.awt.event.FocusEvent;
+import java.text.NumberFormat;
+
+import javax.swing.JFormattedTextField;
+import javax.swing.text.NumberFormatter;
+
+public class NumberTextField extends JFormattedTextField {
+	
+	 /**
+	 * 
+	 */
+	private static final long serialVersionUID = 1L;
+
+	private static final NumberFormatter getNumberFormatInstance(int min, int max) {
+		NumberFormat format = NumberFormat.getInstance();
+	    NumberFormatter formatter = new NumberFormatter(format);
+	    formatter.setValueClass(Integer.class);
+	    formatter.setMinimum(min);
+	    formatter.setMaximum(max);
+	    formatter.setAllowsInvalid(true);
+	    return formatter;
+	}
+
+	public NumberTextField() {
+		super(getNumberFormatInstance(0, Integer.MAX_VALUE));
+	}
+	
+	public NumberTextField(int min, int max) {
+		super(getNumberFormatInstance(min, max));
+	}
+	
+	@Override
+	protected void processFocusEvent(FocusEvent e) {
+	    String text = getText();
+
+	    super.processFocusEvent(e);
+
+	    if ((text == null || text.length() == 0) &&
+	        getValue() != null)
+	        setValue(null);
+	}
+
+
+}