├── .gitignore
├── ui sketch.pdf
├── diagona-icons-1.0
├── all.png
├── icons
│ ├── 10
│ │ ├── 001.png
│ │ ├── 002.png
│ │ ├── 003.png
│ │ ├── 004.png
│ │ ├── 005.png
│ │ ├── 006.png
│ │ ├── 007.png
│ │ ├── 008.png
│ │ ├── 009.png
│ │ ├── 010.png
│ │ ├── 011.png
│ │ ├── 012.png
│ │ ├── 013.png
│ │ ├── 014.png
│ │ ├── 015.png
│ │ ├── 016.png
│ │ ├── 017.png
│ │ ├── 018.png
│ │ ├── 019.png
│ │ ├── 020.png
│ │ ├── 021.png
│ │ ├── 022.png
│ │ ├── 023.png
│ │ ├── 024.png
│ │ ├── 025.png
│ │ ├── 026.png
│ │ ├── 027.png
│ │ ├── 028.png
│ │ ├── 029.png
│ │ ├── 030.png
│ │ ├── 031.png
│ │ ├── 032.png
│ │ ├── 033.png
│ │ ├── 034.png
│ │ ├── 035.png
│ │ ├── 036.png
│ │ ├── 037.png
│ │ ├── 038.png
│ │ ├── 039.png
│ │ ├── 040.png
│ │ ├── 041.png
│ │ ├── 042.png
│ │ ├── 043.png
│ │ ├── 044.png
│ │ ├── 045.png
│ │ ├── 046.png
│ │ ├── 047.png
│ │ ├── 048.png
│ │ ├── 049.png
│ │ ├── 051.png
│ │ ├── 052.png
│ │ ├── 053.png
│ │ ├── 054.png
│ │ ├── 055.png
│ │ ├── 056.png
│ │ ├── 057.png
│ │ ├── 058.png
│ │ ├── 059.png
│ │ ├── 060.png
│ │ ├── 061.png
│ │ ├── 062.png
│ │ ├── 063.png
│ │ ├── 064.png
│ │ ├── 065.png
│ │ ├── 066.png
│ │ ├── 067.png
│ │ ├── 068.png
│ │ ├── 069.png
│ │ ├── 070.png
│ │ ├── 071.png
│ │ ├── 072.png
│ │ ├── 073.png
│ │ ├── 074.png
│ │ ├── 075.png
│ │ ├── 076.png
│ │ ├── 077.png
│ │ ├── 078.png
│ │ ├── 079.png
│ │ ├── 080.png
│ │ ├── 081.png
│ │ ├── 082.png
│ │ ├── 083.png
│ │ ├── 084.png
│ │ ├── 085.png
│ │ ├── 086.png
│ │ ├── 087.png
│ │ ├── 088.png
│ │ ├── 089.png
│ │ ├── 090.png
│ │ ├── 091.png
│ │ ├── 092.png
│ │ ├── 093.png
│ │ ├── 094.png
│ │ ├── 095.png
│ │ ├── 096.png
│ │ ├── 097.png
│ │ ├── 098.png
│ │ ├── 099.png
│ │ ├── 100.png
│ │ ├── 101.png
│ │ ├── 102.png
│ │ ├── 103.png
│ │ ├── 104.png
│ │ ├── 105.png
│ │ ├── 106.png
│ │ ├── 107.png
│ │ ├── 108.png
│ │ ├── 109.png
│ │ ├── 110.png
│ │ ├── 111.png
│ │ ├── 112.png
│ │ ├── 113.png
│ │ ├── 114.png
│ │ ├── 115.png
│ │ ├── 116.png
│ │ ├── 117.png
│ │ ├── 118.png
│ │ ├── 119.png
│ │ ├── 120.png
│ │ ├── 121.png
│ │ ├── 122.png
│ │ ├── 123.png
│ │ ├── 124.png
│ │ ├── 125.png
│ │ ├── 126.png
│ │ ├── 127.png
│ │ ├── 128.png
│ │ ├── 129.png
│ │ ├── 130.png
│ │ ├── 131.png
│ │ ├── 132.png
│ │ ├── 133.png
│ │ ├── 134.png
│ │ ├── 135.png
│ │ ├── 136.png
│ │ ├── 137.png
│ │ ├── 138.png
│ │ ├── 139.png
│ │ ├── 140.png
│ │ ├── 141.png
│ │ ├── 142.png
│ │ ├── 143.png
│ │ ├── 144.png
│ │ ├── 145.png
│ │ ├── 146.png
│ │ ├── 147.png
│ │ ├── 148.png
│ │ ├── 149.png
│ │ ├── 150.png
│ │ ├── 151.png
│ │ ├── 152.png
│ │ ├── 153.png
│ │ ├── 154.png
│ │ ├── 155.png
│ │ ├── 156.png
│ │ ├── 157.png
│ │ ├── 158.png
│ │ ├── 159.png
│ │ ├── 160.png
│ │ ├── 161.png
│ │ ├── 162.png
│ │ ├── 163.png
│ │ ├── 164.png
│ │ ├── 165.png
│ │ ├── 166.png
│ │ ├── 167.png
│ │ ├── 168.png
│ │ ├── 169.png
│ │ ├── 170.png
│ │ ├── 171.png
│ │ ├── 172.png
│ │ ├── 173.png
│ │ ├── 174.png
│ │ ├── 175.png
│ │ ├── 176.png
│ │ ├── 177.png
│ │ ├── 178.png
│ │ ├── 179.png
│ │ ├── 180.png
│ │ ├── 181.png
│ │ ├── 182.png
│ │ ├── 183.png
│ │ ├── 184.png
│ │ ├── 185.png
│ │ ├── 186.png
│ │ ├── 187.png
│ │ ├── 188.png
│ │ ├── 189.png
│ │ ├── 190.png
│ │ ├── 191.png
│ │ ├── 192.png
│ │ ├── 193.png
│ │ ├── 194.png
│ │ ├── 195.png
│ │ ├── 196.png
│ │ ├── 197.png
│ │ ├── 198.png
│ │ ├── 199.png
│ │ ├── 200.png
│ │ └── invalid_dollars.png
│ └── 16
│ │ ├── 001.png
│ │ ├── 002.png
│ │ ├── 003.png
│ │ ├── 004.png
│ │ ├── 005.png
│ │ ├── 006.png
│ │ ├── 007.png
│ │ ├── 008.png
│ │ ├── 009.png
│ │ ├── 010.png
│ │ ├── 011.png
│ │ ├── 012.png
│ │ ├── 013.png
│ │ ├── 014.png
│ │ ├── 015.png
│ │ ├── 016.png
│ │ ├── 017.png
│ │ ├── 018.png
│ │ ├── 019.png
│ │ ├── 020.png
│ │ ├── 021.png
│ │ ├── 022.png
│ │ ├── 023.png
│ │ ├── 024.png
│ │ ├── 025.png
│ │ ├── 026.png
│ │ ├── 027.png
│ │ ├── 028.png
│ │ ├── 029.png
│ │ ├── 030.png
│ │ ├── 031.png
│ │ ├── 032.png
│ │ ├── 033.png
│ │ ├── 034.png
│ │ ├── 035.png
│ │ ├── 036.png
│ │ ├── 037.png
│ │ ├── 038.png
│ │ ├── 039.png
│ │ ├── 040.png
│ │ ├── 041.png
│ │ ├── 042.png
│ │ ├── 043.png
│ │ ├── 044.png
│ │ ├── 045.png
│ │ ├── 046.png
│ │ ├── 047.png
│ │ ├── 048.png
│ │ ├── 049.png
│ │ ├── 050.png
│ │ ├── 051.png
│ │ ├── 052.png
│ │ ├── 053.png
│ │ ├── 054.png
│ │ ├── 055.png
│ │ ├── 056.png
│ │ ├── 057.png
│ │ ├── 058.png
│ │ ├── 059.png
│ │ ├── 060.png
│ │ ├── 061.png
│ │ ├── 062.png
│ │ ├── 063.png
│ │ ├── 064.png
│ │ ├── 065.png
│ │ ├── 066.png
│ │ ├── 067.png
│ │ ├── 068.png
│ │ ├── 069.png
│ │ ├── 070.png
│ │ ├── 071.png
│ │ ├── 072.png
│ │ ├── 073.png
│ │ ├── 074.png
│ │ ├── 075.png
│ │ ├── 076.png
│ │ ├── 077.png
│ │ ├── 078.png
│ │ ├── 079.png
│ │ ├── 080.png
│ │ ├── 081.png
│ │ ├── 082.png
│ │ ├── 083.png
│ │ ├── 084.png
│ │ ├── 085.png
│ │ ├── 086.png
│ │ ├── 087.png
│ │ ├── 088.png
│ │ ├── 089.png
│ │ ├── 090.png
│ │ ├── 091.png
│ │ ├── 092.png
│ │ ├── 093.png
│ │ ├── 094.png
│ │ ├── 095.png
│ │ ├── 096.png
│ │ ├── 097.png
│ │ ├── 098.png
│ │ ├── 099.png
│ │ ├── 100.png
│ │ ├── 101.png
│ │ ├── 102.png
│ │ ├── 103.png
│ │ ├── 104.png
│ │ ├── 105.png
│ │ ├── 106.png
│ │ ├── 107.png
│ │ ├── 108.png
│ │ ├── 109.png
│ │ ├── 110.png
│ │ ├── 111.png
│ │ ├── 112.png
│ │ ├── 113.png
│ │ ├── 114.png
│ │ ├── 115.png
│ │ ├── 116.png
│ │ ├── 117.png
│ │ ├── 118.png
│ │ ├── 119.png
│ │ ├── 120.png
│ │ ├── 121.png
│ │ ├── 122.png
│ │ ├── 123.png
│ │ ├── 124.png
│ │ ├── 125.png
│ │ ├── 126.png
│ │ ├── 127.png
│ │ ├── 128.png
│ │ ├── 129.png
│ │ ├── 130.png
│ │ ├── 131.png
│ │ ├── 132.png
│ │ ├── 133.png
│ │ ├── 134.png
│ │ ├── 135.png
│ │ ├── 136.png
│ │ ├── 137.png
│ │ ├── 138.png
│ │ ├── 139.png
│ │ ├── 140.png
│ │ ├── 141.png
│ │ ├── 142.png
│ │ ├── 143.png
│ │ ├── 144.png
│ │ ├── 145.png
│ │ ├── 146.png
│ │ ├── 147.png
│ │ ├── 148.png
│ │ ├── 149.png
│ │ ├── 150.png
│ │ ├── 151.png
│ │ ├── 152.png
│ │ ├── 153.png
│ │ ├── 154.png
│ │ ├── 155.png
│ │ ├── 156.png
│ │ ├── 157.png
│ │ ├── 158.png
│ │ ├── 159.png
│ │ ├── 160.png
│ │ ├── 161.png
│ │ ├── 162.png
│ │ ├── 163.png
│ │ ├── 164.png
│ │ ├── 165.png
│ │ ├── 166.png
│ │ ├── 167.png
│ │ ├── 168.png
│ │ ├── 169.png
│ │ ├── 170.png
│ │ ├── 171.png
│ │ ├── 172.png
│ │ ├── 173.png
│ │ ├── 174.png
│ │ ├── 175.png
│ │ ├── 176.png
│ │ ├── 177.png
│ │ ├── 178.png
│ │ ├── 179.png
│ │ ├── 180.png
│ │ ├── 181.png
│ │ ├── 182.png
│ │ ├── 183.png
│ │ ├── 184.png
│ │ ├── 185.png
│ │ ├── 186.png
│ │ ├── 187.png
│ │ ├── 188.png
│ │ ├── 189.png
│ │ ├── 190.png
│ │ ├── 191.png
│ │ ├── 192.png
│ │ ├── 193.png
│ │ ├── 194.png
│ │ ├── 195.png
│ │ ├── 196.png
│ │ ├── 197.png
│ │ ├── 198.png
│ │ ├── 199.png
│ │ └── 200.png
└── readme.txt
├── lib
├── mockito-all-1.8.5.jar
├── miglayout-3.7.4-swing.jar
└── miglayout-3.7.4-javadoc.jar
├── run.sh
├── src
└── com
│ └── jamesshore
│ ├── spikes
│ ├── swt
│ │ ├── swt.jar
│ │ ├── readme.txt
│ │ └── SwtSpike.java
│ ├── icon_in_table
│ │ ├── star.gif
│ │ ├── readme.txt
│ │ ├── SpikeDollars.java
│ │ └── IconInTableSpike.java
│ ├── icon_in_textfield
│ │ ├── invalid_dollars.png
│ │ ├── readme.txt
│ │ └── IconInTextField.java
│ ├── overflow
│ │ ├── readme.txt
│ │ └── OverflowSpike.java
│ ├── mac_laf
│ │ └── readme.txt
│ ├── swing
│ │ └── readme.txt
│ ├── looktest
│ │ ├── readme.txt
│ │ ├── CustomTable.java
│ │ ├── _LookSpikeTest.java
│ │ └── LookSpike.java
│ ├── formatted_text
│ │ ├── readme.txt
│ │ ├── SpikeDollars.java
│ │ └── FormattedTextSpike.java
│ ├── colorful_textfield
│ │ ├── readme.txt
│ │ └── ColorfulTextSpike.java
│ ├── mig_layout
│ │ ├── readme.txt
│ │ └── MigLayoutSpike.java
│ ├── save_dialog
│ │ ├── readme.txt
│ │ └── SaveDialogSpike.java
│ └── alert_dialog
│ │ ├── readme.txt
│ │ └── AlertDialogSpike.java
│ └── finances
│ ├── ui
│ ├── resources
│ │ └── invalid_dollars.png
│ ├── RenderTarget.java
│ ├── Resources.java
│ ├── LabelRenderTargetAdapter.java
│ ├── __Invocation.java
│ ├── SaveAsDialog.java
│ ├── ForecastTable.java
│ ├── ConfigurationPanel.java
│ ├── ApplicationModel.java
│ ├── StockMarketTableModel.java
│ ├── _ApplicationModelTest.java
│ ├── ApplicationFrame.java
│ ├── _ConfigurationPanelTest.java
│ ├── _SaveAsDialogTest.java
│ ├── DollarsTextField.java
│ ├── _ForecastTableTest.java
│ └── _DollarsTextFieldTest.java
│ ├── util
│ ├── Require.java
│ ├── RequireException.java
│ ├── NotImplementedException.java
│ ├── UnreachableCodeException.java
│ └── _RequireTest.java
│ ├── values
│ ├── SelfRenderable.java
│ ├── __RenderTargetStub.java
│ ├── _GrowthRateTest.java
│ ├── Year.java
│ ├── _YearTest.java
│ ├── _TaxRateTest.java
│ ├── Dollars.java
│ ├── GrowthRate.java
│ ├── TaxRate.java
│ ├── InvalidDollars.java
│ ├── UserEnteredDollars.java
│ ├── ValidDollars.java
│ └── _InvalidDollarsTest.java
│ ├── Application.java
│ ├── domain
│ ├── StockMarketProjection.java
│ ├── StockMarketYear.java
│ └── _StockMarketProjectionTest.java
│ └── persistence
│ └── UserConfiguration.java
├── .project
├── .settings
└── org.eclipse.jdt.core.prefs
├── .classpath
├── readme.txt
└── scratchpad.txt
/.gitignore:
--------------------------------------------------------------------------------
1 | /bin
2 |
--------------------------------------------------------------------------------
/ui sketch.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/ui sketch.pdf
--------------------------------------------------------------------------------
/diagona-icons-1.0/all.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/all.png
--------------------------------------------------------------------------------
/lib/mockito-all-1.8.5.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/lib/mockito-all-1.8.5.jar
--------------------------------------------------------------------------------
/lib/miglayout-3.7.4-swing.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/lib/miglayout-3.7.4-swing.jar
--------------------------------------------------------------------------------
/lib/miglayout-3.7.4-javadoc.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/lib/miglayout-3.7.4-javadoc.jar
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/001.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/001.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/002.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/002.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/003.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/003.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/004.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/004.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/005.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/005.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/006.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/006.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/007.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/007.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/008.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/008.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/009.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/009.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/010.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/010.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/011.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/011.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/012.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/012.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/013.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/013.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/014.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/014.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/015.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/015.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/016.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/016.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/017.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/017.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/018.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/018.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/019.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/019.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/020.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/020.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/021.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/021.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/022.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/022.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/023.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/023.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/024.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/025.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/025.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/026.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/026.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/027.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/027.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/028.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/028.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/029.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/029.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/030.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/030.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/031.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/031.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/032.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/032.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/033.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/033.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/034.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/034.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/035.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/035.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/036.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/036.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/037.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/037.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/038.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/038.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/039.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/039.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/040.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/040.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/041.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/041.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/042.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/042.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/043.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/043.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/044.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/044.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/045.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/045.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/046.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/046.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/047.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/047.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/048.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/048.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/049.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/049.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/051.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/051.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/052.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/052.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/053.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/053.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/054.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/054.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/055.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/055.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/056.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/056.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/057.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/057.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/058.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/058.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/059.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/059.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/060.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/060.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/061.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/061.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/062.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/062.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/063.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/063.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/064.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/064.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/065.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/065.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/066.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/066.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/067.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/067.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/068.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/068.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/069.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/069.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/070.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/070.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/071.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/071.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/072.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/072.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/073.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/073.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/074.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/074.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/075.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/075.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/076.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/076.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/077.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/077.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/078.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/078.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/079.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/079.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/080.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/080.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/081.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/081.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/082.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/082.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/083.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/083.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/084.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/084.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/085.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/085.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/086.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/086.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/087.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/087.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/088.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/088.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/089.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/089.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/090.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/090.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/091.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/091.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/092.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/092.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/093.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/093.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/094.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/094.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/095.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/095.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/096.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/096.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/097.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/097.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/098.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/098.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/099.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/099.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/100.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/101.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/101.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/102.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/102.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/103.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/103.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/104.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/104.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/105.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/105.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/106.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/106.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/107.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/107.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/108.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/108.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/109.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/109.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/110.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/110.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/111.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/111.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/112.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/112.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/113.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/113.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/114.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/114.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/115.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/115.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/116.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/116.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/117.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/117.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/118.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/118.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/119.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/119.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/120.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/121.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/121.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/122.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/122.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/123.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/123.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/124.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/124.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/125.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/125.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/126.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/126.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/127.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/127.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/128.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/129.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/129.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/130.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/130.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/131.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/131.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/132.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/132.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/133.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/133.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/134.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/134.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/135.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/135.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/136.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/136.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/137.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/137.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/138.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/138.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/139.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/139.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/140.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/140.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/141.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/141.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/142.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/142.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/143.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/143.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/144.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/145.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/145.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/146.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/146.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/147.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/147.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/148.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/148.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/149.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/149.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/150.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/151.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/151.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/152.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/153.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/153.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/154.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/154.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/155.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/155.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/156.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/156.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/157.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/157.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/158.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/158.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/159.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/159.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/160.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/160.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/161.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/161.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/162.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/162.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/163.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/163.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/164.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/164.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/165.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/165.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/166.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/166.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/167.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/167.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/168.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/168.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/169.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/169.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/170.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/170.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/171.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/171.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/172.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/172.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/173.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/173.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/174.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/174.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/175.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/175.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/176.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/176.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/177.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/177.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/178.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/178.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/179.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/179.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/180.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/181.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/181.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/182.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/182.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/183.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/183.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/184.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/184.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/185.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/185.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/186.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/186.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/187.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/187.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/188.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/188.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/189.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/189.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/190.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/190.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/191.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/191.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/192.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/193.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/193.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/194.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/194.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/195.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/195.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/196.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/196.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/197.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/197.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/198.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/198.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/199.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/199.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/200.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/001.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/001.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/002.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/002.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/003.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/003.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/004.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/004.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/005.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/005.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/006.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/006.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/007.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/007.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/008.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/008.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/009.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/009.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/010.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/010.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/011.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/011.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/012.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/012.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/013.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/013.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/014.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/014.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/015.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/015.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/016.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/016.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/017.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/017.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/018.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/018.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/019.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/019.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/020.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/020.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/021.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/021.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/022.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/022.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/023.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/023.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/024.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/025.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/025.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/026.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/026.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/027.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/027.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/028.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/028.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/029.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/029.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/030.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/030.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/031.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/031.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/032.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/032.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/033.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/033.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/034.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/034.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/035.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/035.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/036.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/036.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/037.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/037.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/038.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/038.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/039.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/039.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/040.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/040.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/041.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/041.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/042.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/042.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/043.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/043.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/044.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/044.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/045.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/045.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/046.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/046.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/047.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/047.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/048.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/048.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/049.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/049.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/050.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/050.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/051.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/051.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/052.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/052.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/053.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/053.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/054.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/054.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/055.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/055.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/056.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/056.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/057.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/057.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/058.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/058.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/059.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/059.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/060.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/060.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/061.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/061.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/062.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/062.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/063.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/063.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/064.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/064.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/065.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/065.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/066.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/066.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/067.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/067.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/068.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/068.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/069.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/069.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/070.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/070.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/071.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/071.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/072.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/072.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/073.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/073.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/074.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/074.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/075.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/075.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/076.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/076.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/077.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/077.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/078.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/078.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/079.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/079.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/080.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/080.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/081.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/081.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/082.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/082.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/083.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/083.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/084.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/084.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/085.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/085.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/086.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/086.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/087.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/087.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/088.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/088.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/089.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/089.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/090.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/090.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/091.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/091.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/092.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/092.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/093.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/093.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/094.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/094.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/095.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/095.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/096.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/096.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/097.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/097.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/098.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/098.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/099.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/099.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/100.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/101.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/101.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/102.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/102.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/103.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/103.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/104.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/104.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/105.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/105.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/106.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/106.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/107.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/107.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/108.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/108.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/109.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/109.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/110.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/110.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/111.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/111.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/112.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/112.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/113.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/113.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/114.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/114.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/115.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/115.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/116.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/116.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/117.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/117.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/118.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/118.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/119.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/119.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/120.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/121.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/121.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/122.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/122.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/123.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/123.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/124.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/124.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/125.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/125.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/126.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/126.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/127.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/127.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/128.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/129.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/129.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/130.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/130.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/131.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/131.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/132.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/132.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/133.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/133.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/134.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/134.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/135.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/135.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/136.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/136.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/137.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/137.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/138.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/138.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/139.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/139.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/140.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/140.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/141.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/141.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/142.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/142.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/143.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/143.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/144.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/145.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/145.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/146.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/146.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/147.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/147.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/148.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/148.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/149.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/149.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/150.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/151.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/151.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/152.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/153.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/153.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/154.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/154.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/155.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/155.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/156.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/156.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/157.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/157.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/158.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/158.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/159.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/159.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/160.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/160.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/161.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/161.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/162.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/162.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/163.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/163.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/164.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/164.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/165.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/165.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/166.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/166.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/167.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/167.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/168.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/168.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/169.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/169.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/170.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/170.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/171.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/171.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/172.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/172.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/173.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/173.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/174.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/174.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/175.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/175.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/176.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/176.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/177.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/177.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/178.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/178.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/179.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/179.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/180.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/181.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/181.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/182.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/182.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/183.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/183.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/184.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/184.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/185.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/185.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/186.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/186.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/187.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/187.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/188.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/188.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/189.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/189.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/190.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/190.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/191.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/191.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/192.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/193.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/193.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/194.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/194.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/195.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/195.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/196.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/196.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/197.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/197.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/198.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/198.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/199.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/199.png
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/16/200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/16/200.png
--------------------------------------------------------------------------------
/run.sh:
--------------------------------------------------------------------------------
1 | java -classpath lib/miglayout-3.7.4-swing.jar:bin -Dapple.laf.useScreenMenuBar=true com.jamesshore.finances.Application
2 |
--------------------------------------------------------------------------------
/src/com/jamesshore/spikes/swt/swt.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/src/com/jamesshore/spikes/swt/swt.jar
--------------------------------------------------------------------------------
/diagona-icons-1.0/icons/10/invalid_dollars.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/diagona-icons-1.0/icons/10/invalid_dollars.png
--------------------------------------------------------------------------------
/src/com/jamesshore/spikes/icon_in_table/star.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/src/com/jamesshore/spikes/icon_in_table/star.gif
--------------------------------------------------------------------------------
/src/com/jamesshore/finances/ui/resources/invalid_dollars.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/src/com/jamesshore/finances/ui/resources/invalid_dollars.png
--------------------------------------------------------------------------------
/src/com/jamesshore/spikes/icon_in_textfield/invalid_dollars.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jamesshore/lets_play_tdd/HEAD/src/com/jamesshore/spikes/icon_in_textfield/invalid_dollars.png
--------------------------------------------------------------------------------
/src/com/jamesshore/spikes/overflow/readme.txt:
--------------------------------------------------------------------------------
1 | To run this spike, use this command line from the project root:
2 |
3 | java -classpath bin com.jamesshore.spikes.overflow.OverflowSpike
4 |
--------------------------------------------------------------------------------
/src/com/jamesshore/spikes/mac_laf/readme.txt:
--------------------------------------------------------------------------------
1 | To run the Swing spike, use this command line from the project root:
2 |
3 | java -classpath bin com.jamesshore.spikes.mac_laf.MacSpike
4 |
5 | The Swing spike cannot be run from within Eclipse.
--------------------------------------------------------------------------------
/src/com/jamesshore/spikes/swt/readme.txt:
--------------------------------------------------------------------------------
1 | To run the SWT spike, run from within Eclipse, or use the following command at the project root:
2 |
3 | java -XstartOnFirstThread -classpath src/com/jamesshore/spikes/swt/swt.jar:bin com.jamesshore.spikes.swt.SwtSpike
--------------------------------------------------------------------------------
/src/com/jamesshore/spikes/swing/readme.txt:
--------------------------------------------------------------------------------
1 | To run the Swing spike, use this command line from the project root:
2 |
3 | java -classpath bin -Dapple.laf.useScreenMenuBar=true com.jamesshore.spikes.swing.SwingSpike
4 |
5 | The Swing spike cannot be run from within Eclipse.
--------------------------------------------------------------------------------
/src/com/jamesshore/spikes/looktest/readme.txt:
--------------------------------------------------------------------------------
1 | To run the Swing spike, use this command line from the project root:
2 |
3 | java -classpath bin -Dapple.laf.useScreenMenuBar=true com.jamesshore.spikes.looktest.LookSpike
4 |
5 | The Swing spike cannot be run from within Eclipse.
--------------------------------------------------------------------------------
/src/com/jamesshore/finances/util/Require.java:
--------------------------------------------------------------------------------
1 | package com.jamesshore.finances.util;
2 |
3 | public class Require {
4 |
5 | public static void that(boolean expression, String message) {
6 | if (!expression) throw new RequireException(message);
7 |
8 | }
9 |
10 | }
--------------------------------------------------------------------------------
/src/com/jamesshore/finances/values/SelfRenderable.java:
--------------------------------------------------------------------------------
1 | package com.jamesshore.finances.values;
2 |
3 | import com.jamesshore.finances.ui.*;
4 |
5 | public interface SelfRenderable {
6 |
7 | public void render(Resources resources, RenderTarget target);
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/src/com/jamesshore/spikes/formatted_text/readme.txt:
--------------------------------------------------------------------------------
1 | To run the Swing spike, use this command line from the project root:
2 |
3 | java -classpath bin -Dapple.laf.useScreenMenuBar=true com.jamesshore.spikes.formatted_text.FormattedTextSpike
4 |
5 | The Swing spike cannot be run from within Eclipse.
--------------------------------------------------------------------------------
/src/com/jamesshore/spikes/icon_in_table/readme.txt:
--------------------------------------------------------------------------------
1 | To run the Swing spike, use this command line from the project root:
2 |
3 | java -classpath bin -Dapple.laf.useScreenMenuBar=true com.jamesshore.spikes.icon_in_table.IconInTableSpike
4 |
5 | The Swing spike cannot be run from within Eclipse.
--------------------------------------------------------------------------------
/src/com/jamesshore/spikes/icon_in_textfield/readme.txt:
--------------------------------------------------------------------------------
1 | To run the Swing spike, use this command line from the project root:
2 |
3 | java -classpath bin -Dapple.laf.useScreenMenuBar=true com.jamesshore.spikes.icon_in_textfield.IconInTextField
4 |
5 | The Swing spike cannot be run from within Eclipse.
--------------------------------------------------------------------------------
/src/com/jamesshore/spikes/colorful_textfield/readme.txt:
--------------------------------------------------------------------------------
1 | To run the Swing spike, use this command line from the project root:
2 |
3 | java -classpath bin -Dapple.laf.useScreenMenuBar=true com.jamesshore.spikes.colorful_textfield.ColorfulTextSpike
4 |
5 | The Swing spike cannot be run from within Eclipse.
--------------------------------------------------------------------------------
/src/com/jamesshore/finances/util/RequireException.java:
--------------------------------------------------------------------------------
1 | package com.jamesshore.finances.util;
2 |
3 | public class RequireException extends RuntimeException {
4 | private static final long serialVersionUID = 1L;
5 |
6 | public RequireException(String message) {
7 | super(message);
8 | }
9 |
10 | }
--------------------------------------------------------------------------------
/src/com/jamesshore/spikes/mig_layout/readme.txt:
--------------------------------------------------------------------------------
1 | To run the Swing spike, use this command line from the project root:
2 |
3 | java -classpath lib/miglayout-3.7.4-swing.jar:bin -Dapple.laf.useScreenMenuBar=true com.jamesshore.spikes.mig_layout.MigLayoutSpike
4 |
5 | The Swing spike cannot be run from within Eclipse.
--------------------------------------------------------------------------------
/src/com/jamesshore/spikes/save_dialog/readme.txt:
--------------------------------------------------------------------------------
1 | To run the Swing spike, use this command line from the project root:
2 |
3 | java -classpath lib/miglayout-3.7.4-swing.jar:bin -Dapple.laf.useScreenMenuBar=true com.jamesshore.spikes.save_dialog.SaveDialogSpike
4 |
5 | The Swing spike cannot be run from within Eclipse.
--------------------------------------------------------------------------------
/src/com/jamesshore/finances/util/NotImplementedException.java:
--------------------------------------------------------------------------------
1 | package com.jamesshore.finances.util;
2 |
3 | public class NotImplementedException extends RuntimeException {
4 | private static final long serialVersionUID = 1L;
5 |
6 | public NotImplementedException() {
7 | super("Unimplemented code was executed");
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/com/jamesshore/finances/util/UnreachableCodeException.java:
--------------------------------------------------------------------------------
1 | package com.jamesshore.finances.util;
2 |
3 | public class UnreachableCodeException extends RuntimeException {
4 | private static final long serialVersionUID = 1L;
5 |
6 | public UnreachableCodeException() {
7 | super("Unreachable code was executed");
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/com/jamesshore/spikes/alert_dialog/readme.txt:
--------------------------------------------------------------------------------
1 | To run the Swing spike, use this command line from the project root:
2 |
3 | java -classpath lib/miglayout-3.7.4-swing.jar:lib/task-dialog-1.3.5.jar:bin -Dapple.laf.useScreenMenuBar=true com.jamesshore.spikes.alert_dialog.AlertDialogSpike
4 |
5 | The Swing spike cannot be run from within Eclipse.
--------------------------------------------------------------------------------
/src/com/jamesshore/finances/ui/RenderTarget.java:
--------------------------------------------------------------------------------
1 | package com.jamesshore.finances.ui;
2 |
3 | import java.awt.*;
4 | import javax.swing.*;
5 |
6 | public interface RenderTarget {
7 |
8 | void setText(String text);
9 |
10 | void setIcon(Icon icon, String toolTipText);
11 |
12 | void setForegroundColor(Color color);
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/src/com/jamesshore/spikes/overflow/OverflowSpike.java:
--------------------------------------------------------------------------------
1 | package com.jamesshore.spikes.overflow;
2 |
3 | public class OverflowSpike {
4 |
5 | public static void main(String[] args) {
6 | double a = 9223372036854775807d;
7 | System.out.println(a + "");
8 | System.out.println(a * 10000);
9 | System.out.println(Long.MAX_VALUE);
10 | }
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/src/com/jamesshore/finances/ui/Resources.java:
--------------------------------------------------------------------------------
1 | package com.jamesshore.finances.ui;
2 |
3 | import java.net.*;
4 | import javax.swing.*;
5 |
6 | public class Resources {
7 |
8 | public ImageIcon invalidDollarIcon() {
9 | URL iconUrl = getClass().getResource("resources/invalid_dollars.png");
10 | return new ImageIcon(iconUrl, "Invalid dollar amount");
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/com/jamesshore/finances/Application.java:
--------------------------------------------------------------------------------
1 | package com.jamesshore.finances;
2 |
3 | import javax.swing.*;
4 | import com.jamesshore.finances.ui.*;
5 |
6 | public class Application {
7 |
8 | public static void main(String[] args) {
9 | SwingUtilities.invokeLater(new Runnable() {
10 | public void run() {
11 | ApplicationFrame.newWindow();
12 | }
13 | });
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | Screencast
4 |
5 |
6 |
7 |
8 |
9 | org.eclipse.jdt.core.javabuilder
10 |
11 |
12 |
13 |
14 |
15 | org.eclipse.jdt.core.javanature
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/com/jamesshore/finances/util/_RequireTest.java:
--------------------------------------------------------------------------------
1 | package com.jamesshore.finances.util;
2 |
3 | import static org.junit.Assert.*;
4 | import org.junit.*;
5 |
6 |
7 | public class _RequireTest {
8 |
9 | @Test
10 | public void that() {
11 | try {
12 | Require.that(false, "some message");
13 | fail("expected exception");
14 | }
15 | catch (RequireException e) {
16 | assertEquals("some message", e.getMessage());
17 | }
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/diagona-icons-1.0/readme.txt:
--------------------------------------------------------------------------------
1 | Diagona Icons
2 |
3 | Copyright (C) 2007 Yusuke Kamiyamane. All rights reserved.
4 | The icons are licensed under a Creative Commons Attribution
5 | 3.0 license.
6 |
7 | If you can't or don't want to provide a link back, please
8 | purchase a royalty-free license.
9 |
10 | I'm unavailable for custom icon design work. But your
11 | suggestions are always welcome!
12 |
--------------------------------------------------------------------------------
/src/com/jamesshore/finances/values/__RenderTargetStub.java:
--------------------------------------------------------------------------------
1 | package com.jamesshore.finances.values;
2 |
3 | import java.awt.*;
4 | import javax.swing.*;
5 | import com.jamesshore.finances.ui.*;
6 |
7 | class __RenderTargetStub implements RenderTarget {
8 | public String text;
9 | public Icon icon;
10 | public String toolTipText;
11 | public Color foregroundColor;
12 |
13 | public void setText(String text) {
14 | this.text = text;
15 | }
16 |
17 | public void setIcon(Icon icon, String toolTipText) {
18 | this.icon = icon;
19 | this.toolTipText = toolTipText;
20 | }
21 |
22 | public void setForegroundColor(Color color) {
23 | this.foregroundColor = color;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/.settings/org.eclipse.jdt.core.prefs:
--------------------------------------------------------------------------------
1 | #Tue Aug 31 12:11:08 PDT 2010
2 | eclipse.preferences.version=1
3 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
4 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
5 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
6 | org.eclipse.jdt.core.compiler.compliance=1.6
7 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate
8 | org.eclipse.jdt.core.compiler.debug.localVariable=generate
9 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate
10 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
11 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
12 | org.eclipse.jdt.core.compiler.source=1.6
13 |
--------------------------------------------------------------------------------
/src/com/jamesshore/finances/ui/LabelRenderTargetAdapter.java:
--------------------------------------------------------------------------------
1 | package com.jamesshore.finances.ui;
2 |
3 | import java.awt.*;
4 | import javax.swing.*;
5 |
6 | class LabelRenderTargetAdapter implements RenderTarget {
7 | private JLabel label;
8 |
9 | public LabelRenderTargetAdapter(JLabel label) {
10 | this.label = label;
11 | }
12 |
13 | @Override
14 | public void setText(String text) {
15 | label.setText(text);
16 | }
17 |
18 | @Override
19 | public void setIcon(Icon icon, String toolTipText) {
20 | label.setIcon(icon);
21 | label.setToolTipText(toolTipText);
22 | }
23 |
24 | @Override
25 | public void setForegroundColor(Color color) {
26 | label.setForeground(color);
27 | }
28 |
29 | }
--------------------------------------------------------------------------------
/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/com/jamesshore/spikes/icon_in_table/SpikeDollars.java:
--------------------------------------------------------------------------------
1 | package com.jamesshore.spikes.icon_in_table;
2 |
3 | public class SpikeDollars {
4 |
5 | public double amount;
6 |
7 | public SpikeDollars(int amount) {
8 | this.amount = amount;
9 | }
10 |
11 | private long roundOffPennies() {
12 | return Math.round(this.amount);
13 | }
14 |
15 | @Override
16 | public String toString() {
17 | return "$" + roundOffPennies() + " DOLLARS";
18 | }
19 |
20 | @Override
21 | public int hashCode() {
22 | return (int) roundOffPennies();
23 | }
24 |
25 | @Override
26 | public boolean equals(Object obj) {
27 | SpikeDollars that = (SpikeDollars) obj;
28 | return this.roundOffPennies() == that.roundOffPennies();
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/src/com/jamesshore/finances/ui/__Invocation.java:
--------------------------------------------------------------------------------
1 | package com.jamesshore.finances.ui;
2 |
3 | import static org.junit.Assert.*;
4 | import java.util.*;
5 | import javax.swing.*;
6 |
7 | abstract class __Invocation {
8 | abstract public void invoke();
9 |
10 | abstract boolean stopWaitingWhen();
11 |
12 | public static void invokeAndWaitFor(String message, int timeout, final __Invocation check) {
13 | SwingUtilities.invokeLater(new Runnable() {
14 | @Override
15 | public void run() {
16 | check.invoke();
17 | }
18 | });
19 |
20 | long startTime = new Date().getTime();
21 | while (!check.stopWaitingWhen()) {
22 | Thread.yield();
23 | long elapsedMilliseconds = new Date().getTime() - startTime;
24 | if (elapsedMilliseconds > timeout) fail("expected " + message + " within " + timeout + " milliseconds");
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/src/com/jamesshore/finances/values/_GrowthRateTest.java:
--------------------------------------------------------------------------------
1 | package com.jamesshore.finances.values;
2 |
3 | import static org.junit.Assert.*;
4 | import org.junit.*;
5 |
6 |
7 | public class _GrowthRateTest {
8 |
9 | @Test
10 | public void interest() {
11 | GrowthRate rate = new GrowthRate(10);
12 | assertEquals(new ValidDollars(100), rate.growthFor(new ValidDollars(1000)));
13 | }
14 |
15 | @Test
16 | public void valueObject() {
17 | GrowthRate rate1a = new GrowthRate(10);
18 | GrowthRate rate1b = new GrowthRate(10);
19 | GrowthRate rate2 = new GrowthRate(20);
20 |
21 | assertEquals("10.0%", rate1a.toString());
22 | assertTrue("same rates are equal", rate1a.equals(rate1b));
23 | assertFalse("different rates are not equal", rate1a.equals(rate2));
24 | assertTrue("same rates have same hash code", rate1a.hashCode() == rate1b.hashCode());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/com/jamesshore/finances/values/Year.java:
--------------------------------------------------------------------------------
1 | package com.jamesshore.finances.values;
2 |
3 | public class Year {
4 |
5 | private final int year;
6 |
7 | public Year(int year) {
8 | this.year = year;
9 | }
10 |
11 | public Year nextYear() {
12 | return new Year(year + 1);
13 | }
14 |
15 | public int numberOfYearsInclusive(Year endingYear) {
16 | return endingYear.year - this.year + 1;
17 | }
18 |
19 | public String toString() {
20 | return "" + year;
21 | }
22 |
23 | @Override
24 | public int hashCode() {
25 | final int prime = 31;
26 | int result = 1;
27 | result = prime * result + year;
28 | return result;
29 | }
30 |
31 | @Override
32 | public boolean equals(Object obj) {
33 | if (this == obj) return true;
34 | if (obj == null) return false;
35 | if (getClass() != obj.getClass()) return false;
36 | Year other = (Year) obj;
37 | if (year != other.year) return false;
38 | return true;
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/src/com/jamesshore/spikes/colorful_textfield/ColorfulTextSpike.java:
--------------------------------------------------------------------------------
1 | package com.jamesshore.spikes.colorful_textfield;
2 |
3 | import java.awt.*;
4 | import javax.swing.*;
5 |
6 | public class ColorfulTextSpike extends JFrame {
7 | private static final long serialVersionUID = 1L;
8 |
9 | public ColorfulTextSpike() {
10 | setTitle("Colorful Textfield Spike");
11 | setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
12 | addComponents();
13 | pack();
14 | this.setLocation(400, 200);
15 | }
16 |
17 | private void addComponents() {
18 | Container content = this.getContentPane();
19 | content.add("Center", textField());
20 | }
21 |
22 | private Component textField() {
23 | final JTextField field = new JTextField();
24 | field.setText("foodle");
25 | field.setForeground(Color.RED);
26 | return field;
27 | }
28 |
29 | public static void main(String[] args) {
30 | new ColorfulTextSpike().setVisible(true);
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/src/com/jamesshore/spikes/looktest/CustomTable.java:
--------------------------------------------------------------------------------
1 | package com.jamesshore.spikes.looktest;
2 |
3 | import java.awt.*;
4 | import javax.swing.*;
5 | import javax.swing.table.*;
6 |
7 | public class CustomTable extends JTable {
8 | public static final Color PALE_BLUE = new Color(223, 230, 236);
9 | private static final long serialVersionUID = 1L;
10 |
11 | public CustomTable() {
12 | super();
13 | }
14 |
15 | public CustomTable(DefaultTableModel tableModel) {
16 | super(tableModel);
17 | }
18 |
19 | public Component prepareRenderer(TableCellRenderer renderer, int row, int column) {
20 | // This code based on http://www.roseindia.net/java/example/java/swing/SadingRows.shtml
21 | Component component = super.prepareRenderer(renderer, row, column);
22 | if (isCellSelected(row, column)) return component;
23 |
24 | Color background = row % 2 == 0 ? PALE_BLUE : Color.white;
25 | component.setBackground(background);
26 | return component;
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/src/com/jamesshore/finances/values/_YearTest.java:
--------------------------------------------------------------------------------
1 | package com.jamesshore.finances.values;
2 |
3 | import static org.junit.Assert.*;
4 | import org.junit.*;
5 |
6 |
7 | public class _YearTest {
8 |
9 | @Test
10 | public void nextYear() {
11 | Year thisYear = new Year(2010);
12 | assertEquals(new Year(2011), thisYear.nextYear());
13 | }
14 |
15 | @Test
16 | public void numberOfYearsInclusive() {
17 | Year thisYear = new Year(2010);
18 | assertEquals(41, thisYear.numberOfYearsInclusive(new Year(2050)));
19 | }
20 |
21 | @Test
22 | public void valueObject() {
23 | Year year1a = new Year(2010);
24 | Year year1b = new Year(2010);
25 | Year year2 = new Year(2012);
26 |
27 | assertEquals("2010", year1a.toString());
28 | assertTrue("years with same value should be equal", year1a.equals(year1b));
29 | assertFalse("years with different values should not be equal", year1a.equals(year2));
30 | assertTrue("years with same value should have same hashcode", year1a.hashCode() == year1b.hashCode());
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/src/com/jamesshore/finances/ui/SaveAsDialog.java:
--------------------------------------------------------------------------------
1 | package com.jamesshore.finances.ui;
2 |
3 | import java.awt.*;
4 | import java.io.*;
5 | import javax.swing.*;
6 |
7 | public class SaveAsDialog extends FileDialog {
8 | private static final long serialVersionUID = 1L;
9 |
10 | private ApplicationModel model;
11 |
12 | public SaveAsDialog(Frame parentWindow, ApplicationModel model) {
13 | super(parentWindow, "Save As", FileDialog.SAVE);
14 | this.model = model;
15 | }
16 |
17 | public void displayModally() {
18 | this.setVisible(true);
19 | this.doSave(); // this line of code is untested
20 | }
21 |
22 | // non-private for testing purposes
23 | void doSave() {
24 | try {
25 | String directory = this.getDirectory();
26 | String file = this.getFile();
27 |
28 | if (file != null) model.save(new File(directory, file));
29 | }
30 | catch (IOException e) {
31 | JOptionPane.showMessageDialog(this.getParent(), "Could not save file: " + e.getLocalizedMessage(), "Save File", JOptionPane.WARNING_MESSAGE);
32 | }
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/src/com/jamesshore/finances/values/_TaxRateTest.java:
--------------------------------------------------------------------------------
1 | package com.jamesshore.finances.values;
2 |
3 | import static org.junit.Assert.*;
4 | import org.junit.*;
5 |
6 |
7 | public class _TaxRateTest {
8 |
9 | @Test
10 | public void simpleTaxJustAppliesTaxRateToAmount() {
11 | TaxRate taxRate = new TaxRate(25);
12 | assertEquals(new ValidDollars(250), taxRate.simpleTaxFor(new ValidDollars(1000)));
13 | }
14 |
15 | @Test
16 | public void compoundTaxIsTheAmountOfTaxThatIsIncurredIfYouAlsoPayTaxOnTheTax() {
17 | TaxRate taxRate = new TaxRate(25);
18 | assertEquals(new ValidDollars(333), taxRate.compoundTaxFor(new ValidDollars(1000)));
19 | }
20 |
21 | @Test
22 | public void valueObject() {
23 | TaxRate rate1a = new TaxRate(33);
24 | TaxRate rate1b = new TaxRate(33);
25 | TaxRate rate2 = new TaxRate(40);
26 |
27 | assertEquals("33.0%", rate1a.toString());
28 | assertTrue("same values should be equal", rate1a.equals(rate1b));
29 | assertFalse("different values should not be equal", rate1a.equals(rate2));
30 | assertTrue("same values have same hash code", rate1a.hashCode() == rate1b.hashCode());
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/com/jamesshore/finances/values/Dollars.java:
--------------------------------------------------------------------------------
1 | package com.jamesshore.finances.values;
2 |
3 |
4 | public abstract class Dollars implements SelfRenderable {
5 |
6 | static final double MAX_VALUE = 1000000000d; // one beeeellion dollars!
7 | static final double MIN_VALUE = -1000000000d;
8 |
9 | protected static Dollars create(double amount) {
10 | if (inRange(amount)) return new ValidDollars(amount);
11 | else return new InvalidDollars();
12 | }
13 |
14 | protected static boolean inRange(double value) {
15 | return (value >= MIN_VALUE) && (value <= MAX_VALUE);
16 | }
17 |
18 | public static Dollars min(Dollars value1, Dollars value2) {
19 | return value1.min(value2);
20 | }
21 |
22 | public Dollars flipSign() {
23 | return new ValidDollars(0).minus(this);
24 | }
25 |
26 | public abstract boolean isValid();
27 |
28 | protected abstract double toCoreDataType();
29 |
30 | public abstract Dollars plus(Dollars operand);
31 |
32 | public abstract Dollars minus(Dollars operand);
33 |
34 | public abstract Dollars subtractToZero(Dollars operand);
35 |
36 | public abstract Dollars percentage(double percent);
37 |
38 | public abstract Dollars min(Dollars operand);
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/src/com/jamesshore/spikes/alert_dialog/AlertDialogSpike.java:
--------------------------------------------------------------------------------
1 | package com.jamesshore.spikes.alert_dialog;
2 |
3 | import javax.swing.*;
4 |
5 | public class AlertDialogSpike extends JFrame {
6 | private static final long serialVersionUID = 1L;
7 |
8 | private static String MESSAGE = "Could not frobble the wizzits. We need this dialog to automatically wrap really long lines so that we don't look like we're trapped in the 80's with programmer UI's but instead actually care about the quality of our application, or at least the quality that we convey to our customers, few in number though they may be.";
9 |
10 | public AlertDialogSpike() {
11 | setTitle("Save Dialog Spike");
12 | setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
13 | pack();
14 | this.setLocation(500, 300);
15 | showSwingDialog();
16 | System.exit(0);
17 | }
18 |
19 | private void showSwingDialog() {
20 | JTextPane textPane = new JTextPane();
21 | textPane.setText(MESSAGE);
22 | textPane.setOpaque(false);
23 | JOptionPane.showMessageDialog(this, textPane, "Error", JOptionPane.WARNING_MESSAGE);
24 | }
25 |
26 | public static void main(String[] args) {
27 | new AlertDialogSpike().setVisible(true);
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/src/com/jamesshore/finances/values/GrowthRate.java:
--------------------------------------------------------------------------------
1 | package com.jamesshore.finances.values;
2 |
3 | import com.jamesshore.finances.util.*;
4 |
5 | public class GrowthRate {
6 |
7 | private double rateAsPercentage;
8 |
9 | public GrowthRate(double rateAsPercentage) {
10 | Require.that(rateAsPercentage > 0, "interest rate must be positive (and not zero); was " + rateAsPercentage);
11 | this.rateAsPercentage = rateAsPercentage;
12 | }
13 |
14 | public Dollars growthFor(Dollars amount) {
15 | return amount.percentage(rateAsPercentage);
16 | }
17 |
18 | @Override
19 | public String toString() {
20 | return rateAsPercentage + "%";
21 | }
22 |
23 | @Override
24 | public int hashCode() {
25 | final int prime = 31;
26 | int result = 1;
27 | long temp;
28 | temp = Double.doubleToLongBits(rateAsPercentage);
29 | result = prime * result + (int) (temp ^ (temp >>> 32));
30 | return result;
31 | }
32 |
33 | @Override
34 | public boolean equals(Object obj) {
35 | if (this == obj) return true;
36 | if (obj == null) return false;
37 | if (getClass() != obj.getClass()) return false;
38 | GrowthRate other = (GrowthRate) obj;
39 | if (Double.doubleToLongBits(rateAsPercentage) != Double.doubleToLongBits(other.rateAsPercentage)) return false;
40 | return true;
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/src/com/jamesshore/spikes/mig_layout/MigLayoutSpike.java:
--------------------------------------------------------------------------------
1 | package com.jamesshore.spikes.mig_layout;
2 |
3 | import java.awt.*;
4 | import javax.swing.*;
5 | import net.miginfocom.swing.*;
6 |
7 | public class MigLayoutSpike extends JFrame {
8 | private static final long serialVersionUID = 1L;
9 |
10 | public MigLayoutSpike() {
11 | setTitle("MiGLayout Spike");
12 | setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
13 | addComponents();
14 | pack();
15 | this.setLocation(400, 200);
16 | this.setSize(600, 300);
17 | }
18 |
19 | private void addComponents() {
20 | Container content = this.getContentPane();
21 | content.setLayout(new MigLayout("fillx, wrap 6", "[right]rel[grow]unrel[right]rel[grow]unrel[right]rel[grow]"));
22 |
23 | addField(content, "Starting Balance:");
24 | addField(content, "Starting Year:");
25 | addField(content, "Interest Rate:");
26 | addField(content, "Cost Basis:");
27 | addField(content, "Ending Year:");
28 | addField(content, "Tax Rate:");
29 | addField(content, "Yearly Spending:");
30 | }
31 |
32 | private void addField(Container content, String label) {
33 | content.add(new JLabel(label));
34 | content.add(new JTextField(), "growx, sizegroup");
35 | }
36 |
37 | public static void main(String[] args) {
38 | new MigLayoutSpike().setVisible(true);
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/src/com/jamesshore/finances/domain/StockMarketProjection.java:
--------------------------------------------------------------------------------
1 | package com.jamesshore.finances.domain;
2 |
3 | import com.jamesshore.finances.util.*;
4 | import com.jamesshore.finances.values.*;
5 |
6 | public class StockMarketProjection {
7 |
8 | private final Year startingYear;
9 | private final Year endingYear;
10 | private StockMarketYear[] years;
11 | private final Dollars sellEveryYear;
12 |
13 | public StockMarketProjection(StockMarketYear firstYear, Year endingYear, Dollars sellEveryYear) {
14 | this.startingYear = firstYear.year();
15 | this.endingYear = endingYear;
16 | this.sellEveryYear = sellEveryYear;
17 | populateYears(firstYear);
18 | }
19 |
20 | private void populateYears(StockMarketYear firstYear) {
21 | this.years = new StockMarketYear[numberOfYears()];
22 | years[0] = firstYear;
23 | years[0].sell(sellEveryYear);
24 | for (int i = 1; i < numberOfYears(); i++) {
25 | years[i] = years[i - 1].nextYear();
26 | years[i].sell(sellEveryYear);
27 | }
28 | }
29 |
30 | public int numberOfYears() {
31 | return startingYear.numberOfYearsInclusive(endingYear);
32 | }
33 |
34 | public StockMarketYear getYearOffset(int offset) {
35 | Require.that(offset >= 0 && offset < numberOfYears(), "Offset needs to be between 0 and " + (numberOfYears() - 1) + "; was " + offset);
36 | return years[offset];
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/src/com/jamesshore/spikes/save_dialog/SaveDialogSpike.java:
--------------------------------------------------------------------------------
1 | package com.jamesshore.spikes.save_dialog;
2 |
3 | import java.awt.*;
4 | import java.awt.event.*;
5 | import javax.swing.*;
6 |
7 | public class SaveDialogSpike extends JFrame {
8 | private static final long serialVersionUID = 1L;
9 |
10 | public SaveDialogSpike() {
11 | setTitle("Save Dialog Spike");
12 | setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
13 | addComponents();
14 | pack();
15 | this.setLocation(400, 200);
16 | }
17 |
18 | private void addComponents() {
19 | Container content = this.getContentPane();
20 | JButton button = new JButton("Save Dialog...");
21 | button.addActionListener(new ActionListener() {
22 | @Override
23 | public void actionPerformed(ActionEvent e) {
24 | showSaveDialog();
25 | }
26 | });
27 | content.add(button);
28 | }
29 |
30 | private void showSaveDialog() {
31 | FileDialog dialog = new FileDialog(this, "Save As", FileDialog.SAVE);
32 | dialog.setVisible(true);
33 | String fileSelected = dialog.getFile();
34 | if (fileSelected == null) JOptionPane.showMessageDialog(this, "Cancel button pushed");
35 | else JOptionPane.showMessageDialog(this, "Save button pushed: " + fileSelected);
36 | }
37 |
38 | public static void main(String[] args) {
39 | new SaveDialogSpike().setVisible(true);
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/readme.txt:
--------------------------------------------------------------------------------
1 | Let's Play: Test-Driven Development
2 |
3 | "Let's Play: Test-Driven Development" series is a stream-of-consciousness live recording of my successes and slip-ups as I program a real project, from scratch, using test-driven development. I'm hoping this will be an interesting view into how test-driven development works when you take it out of the classroom. As the series continues, I'm sure you'll see quite a bit of incremental design and architecture, too.
4 |
5 | This git repository contains the source code developed during the series. For the screencasts, see http://jamesshore.com/Blog/Lets-Play/ .
6 |
7 | You can find a Maven POM for this project at https://github.com/pht1/lets_play_tdd/blob/master/pom.xml .
8 |
9 |
10 | Copyright information:
11 |
12 | Except as noted below, all code in this repository is copyright James Shore and licensed under Gnu AGPL 3.
13 |
14 | The icons in the icons/ folder are Copyright Yusuke Kamiyamane. All rights reserved. Licensed under a Creative Commons Attribution 3.0 license.
15 | Find the icons at http://p.yusukekamiyamane.com/. Used in this application: the Diagona Icons set.
16 |
17 | This project uses the MiGLayout library, which may be found at http://www.miglayout.com/ and is licensed under the BSD and GPL licenses.
18 |
19 | This project uses the Mockito library, which may be found at http://mockito.org/ and is licensed under the MIT license.
--------------------------------------------------------------------------------
/src/com/jamesshore/finances/values/TaxRate.java:
--------------------------------------------------------------------------------
1 | package com.jamesshore.finances.values;
2 |
3 | import com.jamesshore.finances.util.*;
4 |
5 | public class TaxRate {
6 |
7 | private double rateAsPercentage;
8 |
9 | public TaxRate(double rateAsPercentage) {
10 | Require.that(rateAsPercentage > 0, "tax rate must be positive (and not zero); was " + rateAsPercentage);
11 | this.rateAsPercentage = rateAsPercentage;
12 | }
13 |
14 | public Dollars simpleTaxFor(Dollars amount) {
15 | return amount.percentage(rateAsPercentage);
16 | }
17 |
18 | public Dollars compoundTaxFor(Dollars amount) {
19 | double compoundRate = (100.0 / (100.0 - rateAsPercentage)) - 1;
20 | return amount.percentage(compoundRate * 100);
21 | }
22 |
23 | @Override
24 | public String toString() {
25 | return (rateAsPercentage) + "%";
26 | }
27 |
28 | @Override
29 | public int hashCode() {
30 | final int prime = 31;
31 | int result = 1;
32 | long temp;
33 | temp = Double.doubleToLongBits(rateAsPercentage);
34 | result = prime * result + (int) (temp ^ (temp >>> 32));
35 | return result;
36 | }
37 |
38 | @Override
39 | public boolean equals(Object obj) {
40 | if (this == obj) return true;
41 | if (obj == null) return false;
42 | if (getClass() != obj.getClass()) return false;
43 | TaxRate other = (TaxRate) obj;
44 | if (Double.doubleToLongBits(rateAsPercentage) != Double.doubleToLongBits(other.rateAsPercentage)) return false;
45 | return true;
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/src/com/jamesshore/finances/values/InvalidDollars.java:
--------------------------------------------------------------------------------
1 | package com.jamesshore.finances.values;
2 |
3 | import com.jamesshore.finances.ui.*;
4 | import com.jamesshore.finances.util.*;
5 |
6 | public class InvalidDollars extends Dollars {
7 |
8 | @Override
9 | public boolean isValid() {
10 | return false;
11 | }
12 |
13 | @Override
14 | protected double toCoreDataType() {
15 | throw new UnreachableCodeException();
16 | }
17 |
18 | @Override
19 | public Dollars plus(Dollars dollars) {
20 | return new InvalidDollars();
21 | }
22 |
23 | @Override
24 | public Dollars minus(Dollars dollars) {
25 | return new InvalidDollars();
26 | }
27 |
28 | @Override
29 | public Dollars subtractToZero(Dollars dollars) {
30 | return new InvalidDollars();
31 | }
32 |
33 | @Override
34 | public Dollars percentage(double percent) {
35 | return new InvalidDollars();
36 | }
37 |
38 | @Override
39 | public Dollars min(Dollars value2) {
40 | return new InvalidDollars();
41 | }
42 |
43 | public void render(Resources resources, RenderTarget target) {
44 | target.setIcon(resources.invalidDollarIcon(), "Invalid dollar amount");
45 | target.setText(null);
46 | }
47 |
48 | @Override
49 | public String toString() {
50 | return "$???";
51 | }
52 |
53 | @Override
54 | public boolean equals(Object obj) {
55 | if (obj == null) return false;
56 | Dollars that = (Dollars)obj;
57 |
58 | return !that.isValid();
59 | }
60 |
61 | @Override
62 | public int hashCode() {
63 | return 13;
64 | }
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/src/com/jamesshore/spikes/formatted_text/SpikeDollars.java:
--------------------------------------------------------------------------------
1 | package com.jamesshore.spikes.formatted_text;
2 |
3 | public class SpikeDollars {
4 |
5 | private double amount;
6 |
7 | public SpikeDollars(int amount) {
8 | this.amount = amount;
9 | }
10 |
11 | public SpikeDollars(double amount) {
12 | this.amount = amount;
13 | }
14 |
15 | public SpikeDollars plus(SpikeDollars dollars) {
16 | return new SpikeDollars(this.amount + dollars.amount);
17 | }
18 |
19 | public SpikeDollars minus(SpikeDollars dollars) {
20 | return new SpikeDollars(this.amount - dollars.amount);
21 | }
22 |
23 | public SpikeDollars subtractToZero(SpikeDollars dollars) {
24 | double result = this.amount - dollars.amount;
25 | return new SpikeDollars(Math.max(0, result));
26 | }
27 |
28 | public SpikeDollars percentage(double percent) {
29 | return new SpikeDollars(amount * percent / 100.0);
30 | }
31 |
32 | public static SpikeDollars min(SpikeDollars value1, SpikeDollars value2) {
33 | return new SpikeDollars(Math.min(value1.amount, value2.amount));
34 | }
35 |
36 | private long roundOffPennies() {
37 | return Math.round(this.amount);
38 | }
39 |
40 | @Override
41 | public String toString() {
42 | return "$" + roundOffPennies() + " DOLLARS";
43 | }
44 |
45 | @Override
46 | public int hashCode() {
47 | return (int)roundOffPennies();
48 | }
49 |
50 | @Override
51 | public boolean equals(Object obj) {
52 | SpikeDollars that = (SpikeDollars)obj;
53 | return this.roundOffPennies() == that.roundOffPennies();
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/src/com/jamesshore/finances/ui/ForecastTable.java:
--------------------------------------------------------------------------------
1 | package com.jamesshore.finances.ui;
2 |
3 | import java.awt.*;
4 | import javax.swing.*;
5 | import javax.swing.table.*;
6 | import com.jamesshore.finances.values.*;
7 |
8 | //If you want to subclass this class, it's okay to remove the 'final designator, but be careful of race
9 | //conditions with the cell renderer in the constructor. It could execute before the subclass constructor.
10 | public final class ForecastTable extends JTable {
11 | private static final long serialVersionUID = 1L;
12 |
13 | public static final Color STANDARD_BACKGROUND_COLOR = Color.WHITE;
14 | public static final Color ALTERNATE_BACKGROUND_COLOR = new Color(209, 229, 255);
15 | public static final Color SELECTION_BACKGROUND_COLOR = new Color(52, 117, 237);
16 |
17 | public ForecastTable(TableModel model) {
18 | super(model);
19 | setDefaultRenderer(SelfRenderable.class, selfRenderer());
20 | }
21 |
22 | private TableCellRenderer selfRenderer() {
23 | return new DefaultTableCellRenderer() {
24 | private static final long serialVersionUID = 1L;
25 |
26 | public void setValue(Object value) {
27 | SelfRenderable renderable = (SelfRenderable)value;
28 | renderable.render(new Resources(), new LabelRenderTargetAdapter(this));
29 | }
30 | };
31 | }
32 |
33 | @Override
34 | public Component prepareRenderer(TableCellRenderer renderer, int row, int column) {
35 | Component cell = super.prepareRenderer(renderer, row, column);
36 |
37 | if (isCellSelected(row, column)) cell.setBackground(SELECTION_BACKGROUND_COLOR);
38 | else if (alternatingRow(row)) cell.setBackground(ALTERNATE_BACKGROUND_COLOR);
39 | else cell.setBackground(STANDARD_BACKGROUND_COLOR);
40 |
41 | return cell;
42 | }
43 |
44 | private boolean alternatingRow(int row) {
45 | return row % 2 == 1;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/com/jamesshore/spikes/looktest/_LookSpikeTest.java:
--------------------------------------------------------------------------------
1 | package com.jamesshore.spikes.looktest;
2 |
3 | import java.awt.*;
4 | import javax.swing.*;
5 | import javax.swing.table.*;
6 | import org.junit.*;
7 |
8 | public class _LookSpikeTest {
9 |
10 | private JFrame frame;
11 | private CustomTable table;
12 |
13 | @Before
14 | public void setup() {
15 | frame = new JFrame();
16 | DefaultTableModel tableModel = new DefaultTableModel(0, 3);
17 | tableModel.addRow(new String[] { "a", "b", "c" });
18 | tableModel.addRow(new String[] { "1", "2", "3" });
19 | tableModel.addRow(new String[] { "I", "II", "III" });
20 | frame.getContentPane().add(new CustomTable(tableModel));
21 | frame.setVisible(true);
22 | frame.pack();
23 | table = (CustomTable)frame.getContentPane().getComponent(0);
24 | }
25 |
26 | @After
27 | public void teardown() {
28 | frame.setVisible(false);
29 | }
30 |
31 | // @Test
32 | // public void nothing() throws Exception {
33 | // assertEquals(CustomTable.PALE_BLUE, backgroundOfCell(0, 0));
34 | // assertEquals(CustomTable.PALE_BLUE, backgroundOfCell(0, 1));
35 | // assertEquals(CustomTable.PALE_BLUE, backgroundOfCell(0, 2));
36 | // assertEquals(Color.WHITE, backgroundOfCell(1, 0));
37 | // assertEquals(Color.WHITE, backgroundOfCell(1, 1));
38 | // assertEquals(Color.WHITE, backgroundOfCell(1, 2));
39 | // assertEquals(CustomTable.PALE_BLUE, backgroundOfCell(2, 0));
40 | // assertEquals(CustomTable.PALE_BLUE, backgroundOfCell(2, 1));
41 | // assertEquals(CustomTable.PALE_BLUE, backgroundOfCell(2, 2));
42 | // }
43 |
44 | @SuppressWarnings("unused")
45 | private Color backgroundOfCell(int row, int column) {
46 | TableCellRenderer renderer = table.getCellRenderer(row, column);
47 | Component component = table.prepareRenderer(renderer, row, column);
48 | Color actualColor = component.getBackground();
49 | return actualColor;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/com/jamesshore/finances/ui/ConfigurationPanel.java:
--------------------------------------------------------------------------------
1 | package com.jamesshore.finances.ui;
2 |
3 | import javax.swing.*;
4 | import net.miginfocom.swing.*;
5 | import com.jamesshore.finances.persistence.*;
6 | import com.jamesshore.finances.ui.DollarsTextField.ChangeListener;
7 |
8 | public class ConfigurationPanel extends JPanel {
9 | private static final long serialVersionUID = 1L;
10 | private UserConfiguration userConfiguration;
11 |
12 | public ConfigurationPanel(UserConfiguration userConfiguration) {
13 | this.userConfiguration = userConfiguration;
14 | addComponents();
15 | }
16 |
17 | private void addComponents() {
18 | this.setLayout(new MigLayout("fillx, wrap 2", "[right]rel[grow]"));
19 | addField("Starting Balance:", startingBalanceField());
20 | addField("Cost Basis:", costBasisField());
21 | addField("Yearly Spending:", yearlySpendingField());
22 | }
23 |
24 | private void addField(String name, DollarsTextField field) {
25 | this.add(new JLabel(name));
26 | this.add(field, "growx");
27 | }
28 |
29 | public DollarsTextField startingBalanceField() {
30 | final DollarsTextField field = new DollarsTextField(userConfiguration.getStartingBalance());
31 | field.addTextChangeListener(new ChangeListener() {
32 | public void textChanged() {
33 | userConfiguration.setStartingBalance(field.getDollars());
34 | }
35 | });
36 | return field;
37 | }
38 |
39 | private DollarsTextField costBasisField() {
40 | final DollarsTextField field = new DollarsTextField(userConfiguration.getStartingCostBasis());
41 | field.addTextChangeListener(new ChangeListener() {
42 | public void textChanged() {
43 | userConfiguration.setStartingCostBasis(field.getDollars());
44 | }
45 | });
46 | return field;
47 | }
48 |
49 | private DollarsTextField yearlySpendingField() {
50 | final DollarsTextField field = new DollarsTextField(userConfiguration.getYearlySpending());
51 | field.addTextChangeListener(new ChangeListener() {
52 | public void textChanged() {
53 | userConfiguration.setYearlySpending(field.getDollars());
54 | }
55 | });
56 | return field;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/com/jamesshore/finances/ui/ApplicationModel.java:
--------------------------------------------------------------------------------
1 | package com.jamesshore.finances.ui;
2 |
3 | import java.io.*;
4 | import com.jamesshore.finances.domain.*;
5 | import com.jamesshore.finances.persistence.*;
6 | import com.jamesshore.finances.persistence.UserConfiguration.Observer;
7 | import com.jamesshore.finances.values.*;
8 |
9 | public class ApplicationModel implements Observer {
10 |
11 | public static final Year DEFAULT_STARTING_YEAR = new Year(2010);
12 | public static final Year DEFAULT_ENDING_YEAR = new Year(2050);
13 | public static final GrowthRate DEFAULT_GROWTH_RATE = new GrowthRate(10);
14 | public static final TaxRate DEFAULT_CAPITAL_GAINS_TAX_RATE = new TaxRate(25);
15 |
16 | private Year startingYear = DEFAULT_STARTING_YEAR;
17 | private Year endingYear = DEFAULT_ENDING_YEAR;
18 | private GrowthRate growthRate = DEFAULT_GROWTH_RATE;
19 | private TaxRate capitalGainsTaxRate = DEFAULT_CAPITAL_GAINS_TAX_RATE;
20 |
21 | private UserConfiguration configuration;
22 | private StockMarketTableModel stockMarketTableModel;
23 |
24 | public ApplicationModel() {
25 | this.configuration = new UserConfiguration();
26 | this.configuration.setObserver(this);
27 | this.stockMarketTableModel = new StockMarketTableModel(stockMarketProjection());
28 | }
29 |
30 | public UserConfiguration userConfiguration() {
31 | return configuration;
32 | }
33 |
34 | public StockMarketTableModel stockMarketTableModel() {
35 | return stockMarketTableModel;
36 | }
37 |
38 | @Override
39 | public void configurationUpdated() {
40 | stockMarketTableModel.setProjection(stockMarketProjection());
41 | }
42 |
43 | public StockMarketProjection stockMarketProjection() {
44 | StockMarketYear firstYear = new StockMarketYear(startingYear, configuration.getStartingBalance(), configuration.getStartingCostBasis(), growthRate, capitalGainsTaxRate);
45 | return new StockMarketProjection(firstYear, endingYear, configuration.getYearlySpending());
46 | }
47 |
48 | public void save(File path) throws IOException {
49 | configuration.save(path);
50 | }
51 |
52 | public File lastSavedPathOrNullIfNeverSaved() {
53 | return configuration.lastSavedPathOrNullIfNeverSaved();
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/com/jamesshore/finances/domain/StockMarketYear.java:
--------------------------------------------------------------------------------
1 | package com.jamesshore.finances.domain;
2 |
3 | import com.jamesshore.finances.values.*;
4 |
5 | public class StockMarketYear {
6 |
7 | private Year year;
8 | private Dollars startingBalance;
9 | private Dollars costBasis;
10 | private GrowthRate growthRate;
11 | private TaxRate capitalGainsTaxRate;
12 | private Dollars totalSellOrders;
13 |
14 | public StockMarketYear(Year year, Dollars startingBalance, Dollars costBasis, GrowthRate growthRate, TaxRate capitalGainsTaxRate) {
15 | this.year = year;
16 | this.startingBalance = startingBalance;
17 | this.costBasis = costBasis;
18 | this.growthRate = growthRate;
19 | this.capitalGainsTaxRate = capitalGainsTaxRate;
20 | this.totalSellOrders = new ValidDollars(0);
21 | }
22 |
23 | public Year year() {
24 | return year;
25 | }
26 |
27 | public Dollars startingBalance() {
28 | return startingBalance;
29 | }
30 |
31 | public Dollars startingCostBasis() {
32 | return costBasis;
33 | }
34 |
35 | private Dollars startingCapitalGains() {
36 | return startingBalance().minus(startingCostBasis());
37 | }
38 |
39 | public GrowthRate growthRate() {
40 | return growthRate;
41 | }
42 |
43 | public TaxRate capitalGainsTaxRate() {
44 | return capitalGainsTaxRate;
45 | }
46 |
47 | public void sell(Dollars amount) {
48 | this.totalSellOrders = totalSellOrders.plus(amount);
49 | }
50 |
51 | private Dollars capitalGainsWithdrawn() {
52 | return Dollars.min(startingCapitalGains(), totalSellOrders());
53 | }
54 |
55 | public Dollars capitalGainsTaxIncurred() {
56 | return capitalGainsTaxRate.compoundTaxFor(capitalGainsWithdrawn());
57 | }
58 |
59 | public Dollars totalSellOrders() {
60 | return totalSellOrders;
61 | }
62 |
63 | public Dollars totalSold() {
64 | return totalSellOrders().plus(capitalGainsTaxIncurred());
65 | }
66 |
67 | public Dollars growth() {
68 | return growthRate.growthFor(startingBalance.minus(totalSold()));
69 | }
70 |
71 | public Dollars endingBalance() {
72 | return startingBalance.minus(totalSold()).plus(growth());
73 | }
74 |
75 | public Dollars endingCostBasis() {
76 | Dollars purchasesSold = totalSold().subtractToZero(startingCapitalGains());
77 | return startingCostBasis().minus(purchasesSold);
78 | }
79 |
80 | public StockMarketYear nextYear() {
81 | return new StockMarketYear(year.nextYear(), this.endingBalance(), this.endingCostBasis(), this.growthRate(), this.capitalGainsTaxRate());
82 | }
83 |
84 | }
85 |
--------------------------------------------------------------------------------
/src/com/jamesshore/finances/domain/_StockMarketProjectionTest.java:
--------------------------------------------------------------------------------
1 | package com.jamesshore.finances.domain;
2 |
3 | import static org.junit.Assert.*;
4 | import org.junit.*;
5 | import com.jamesshore.finances.values.*;
6 |
7 | public class _StockMarketProjectionTest {
8 |
9 | private static final Year STARTING_YEAR = new Year(2010);
10 | private static final Year ENDING_YEAR = new Year(2050);
11 | private static final Dollars STARTING_BALANCE = new ValidDollars(10000);
12 | private static final Dollars COST_BASIS = new ValidDollars(7000);
13 | private static final GrowthRate GROWTH_RATE = new GrowthRate(10);
14 | private static final TaxRate CAPITAL_GAINS_TAX_RATE = new TaxRate(25);
15 |
16 | private StockMarketYear firstYear;
17 |
18 | @Before
19 | public void setup() {
20 | firstYear = new StockMarketYear(STARTING_YEAR, STARTING_BALANCE, COST_BASIS, GROWTH_RATE, CAPITAL_GAINS_TAX_RATE);
21 | }
22 |
23 | @Test
24 | public void stockMarketContainsMultipleYears() {
25 | StockMarketProjection account = new StockMarketProjection(firstYear, ENDING_YEAR, new ValidDollars(0));
26 | assertEquals("# of years", 41, account.numberOfYears());
27 | assertEquals(STARTING_BALANCE, account.getYearOffset(0).startingBalance());
28 | assertEquals(new ValidDollars(11000), account.getYearOffset(1).startingBalance());
29 | assertEquals(new ValidDollars(12100), account.getYearOffset(2).startingBalance());
30 | assertEquals(new Year(2050), account.getYearOffset(40).year());
31 | }
32 |
33 | @Test
34 | public void stockMarketWithdrawsAStandardAmountEveryYear() {
35 | StockMarketProjection account = new StockMarketProjection(firstYear, ENDING_YEAR, new ValidDollars(10));
36 | assertEquals("year 0", new ValidDollars(10), account.getYearOffset(0).totalSellOrders());
37 | assertEquals("year 1", new ValidDollars(10), account.getYearOffset(1).totalSellOrders());
38 | assertEquals("year 40", new ValidDollars(10), account.getYearOffset(40).totalSellOrders());
39 | }
40 |
41 | @Test
42 | public void noCumulativeRoundingErrorInInterestCalculations() {
43 | StockMarketProjection account = new StockMarketProjection(firstYear, ENDING_YEAR, new ValidDollars(0));
44 | assertEquals(new ValidDollars(497852), account.getYearOffset(40).endingBalance());
45 | }
46 |
47 | @Test
48 | public void capitalGainsTaxCalculationWorksTheSameWayAsSpreadsheet() {
49 | StockMarketProjection account = new StockMarketProjection(firstYear, ENDING_YEAR, new ValidDollars(695));
50 | assertEquals(new ValidDollars(2067), account.getYearOffset(40).endingBalance());
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/src/com/jamesshore/finances/ui/StockMarketTableModel.java:
--------------------------------------------------------------------------------
1 | package com.jamesshore.finances.ui;
2 |
3 | import javax.swing.table.*;
4 | import com.jamesshore.finances.domain.*;
5 | import com.jamesshore.finances.util.*;
6 | import com.jamesshore.finances.values.*;
7 |
8 | public class StockMarketTableModel extends AbstractTableModel {
9 | private static final long serialVersionUID = 1L;
10 | public static final String[] COLUMN_TITLES = { "Year", "Starting Balance", "Cost Basis", "Sell Orders", "Taxes", "Growth", "Ending Balance" };
11 | public static final Class>[] COLUMN_CLASSES = { Year.class, SelfRenderable.class, SelfRenderable.class, SelfRenderable.class, SelfRenderable.class, SelfRenderable.class, SelfRenderable.class };
12 |
13 | private StockMarketProjection projection;
14 |
15 | public StockMarketTableModel(StockMarketProjection projection) {
16 | this.projection = projection;
17 | }
18 |
19 | public void setProjection(StockMarketProjection projection) {
20 | this.projection = projection;
21 | this.fireTableDataChanged();
22 | }
23 |
24 | public StockMarketProjection stockMarketProjection() {
25 | return projection;
26 | }
27 |
28 | @Override
29 | public int getColumnCount() {
30 | return COLUMN_TITLES.length;
31 | }
32 |
33 | @Override
34 | public String getColumnName(int column) {
35 | return COLUMN_TITLES[column];
36 | }
37 |
38 | @Override
39 | public Class> getColumnClass(int column) {
40 | return COLUMN_CLASSES[column];
41 | }
42 |
43 | @Override
44 | public int getRowCount() {
45 | return projection.numberOfYears();
46 | }
47 |
48 | @Override
49 | public Object getValueAt(int rowIndex, int columnIndex) {
50 | StockMarketYear currentYear = projection.getYearOffset(rowIndex);
51 | switch (columnIndex) {
52 | case 0:
53 | return currentYear.year();
54 | case 1:
55 | return currentYear.startingBalance();
56 | case 2:
57 | return currentYear.startingCostBasis();
58 | case 3:
59 | return currentYear.totalSellOrders().flipSign();
60 | case 4:
61 | return currentYear.capitalGainsTaxIncurred().flipSign();
62 | case 5:
63 | return currentYear.growth();
64 | case 6:
65 | return currentYear.endingBalance();
66 | default:
67 | throw new UnreachableCodeException();
68 | }
69 | }
70 |
71 | public Dollars startingBalance() {
72 | return projection.getYearOffset(0).startingBalance();
73 | }
74 |
75 | public Dollars startingCostBasis() {
76 | return projection.getYearOffset(0).startingCostBasis();
77 | }
78 |
79 | public Dollars yearlySpending() {
80 | return projection.getYearOffset(0).totalSellOrders();
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/com/jamesshore/finances/ui/_ApplicationModelTest.java:
--------------------------------------------------------------------------------
1 | package com.jamesshore.finances.ui;
2 |
3 | import static org.junit.Assert.*;
4 | import java.io.*;
5 | import org.junit.*;
6 | import com.jamesshore.finances.domain.*;
7 | import com.jamesshore.finances.persistence.*;
8 | import com.jamesshore.finances.values.*;
9 |
10 | public class _ApplicationModelTest {
11 |
12 | private UserConfiguration configuration;
13 | private ApplicationModel model;
14 |
15 | @Before
16 | public void setup() {
17 | UserConfiguration.STUB_OUT_FILE_SYSTEM_FOR_TESTING_ONLY = true;
18 | model = new ApplicationModel();
19 | configuration = model.userConfiguration();
20 | }
21 |
22 | @After
23 | public void teardown() {
24 | UserConfiguration.STUB_OUT_FILE_SYSTEM_FOR_TESTING_ONLY = false;
25 | }
26 |
27 | @Test
28 | public void shouldStartWithDefaultStockMarket() {
29 | StockMarketProjection projection = model.stockMarketTableModel().stockMarketProjection();
30 |
31 | StockMarketYear startingYear = projection.getYearOffset(0);
32 | assertEquals(UserConfiguration.DEFAULT_STARTING_BALANCE, startingYear.startingBalance());
33 | assertEquals(UserConfiguration.DEFAULT_STARTING_COST_BASIS, startingYear.startingCostBasis());
34 | assertEquals(UserConfiguration.DEFAULT_YEARLY_SPENDING, startingYear.totalSellOrders());
35 |
36 | assertEquals(ApplicationModel.DEFAULT_STARTING_YEAR, startingYear.year());
37 | assertEquals(ApplicationModel.DEFAULT_GROWTH_RATE, startingYear.growthRate());
38 | assertEquals(ApplicationModel.DEFAULT_CAPITAL_GAINS_TAX_RATE, startingYear.capitalGainsTaxRate());
39 |
40 | assertEquals(41, projection.numberOfYears());
41 | }
42 |
43 | @Test
44 | public void shouldOnlyHaveOneInstanceOfStockMarketTableModel() {
45 | assertTrue("should be same instance", model.stockMarketTableModel() == model.stockMarketTableModel());
46 | }
47 |
48 | @Test
49 | public void configurationUpdated_ResultsInStockMarketTableModelChanging() {
50 | configuration.setYearlySpending(new UserEnteredDollars("423"));
51 | assertEquals("stock market table model", new ValidDollars(423), model.stockMarketTableModel().yearlySpending());
52 | }
53 |
54 | @Test
55 | public void saveShouldSaveConfiguration() throws IOException {
56 | assertNull("configuration should not be saved", configuration.lastSavedPathOrNullIfNeverSaved());
57 | File expectedFile = new File("FOO");
58 | model.save(expectedFile);
59 | assertEquals("configuration save file", expectedFile, configuration.lastSavedPathOrNullIfNeverSaved());
60 | }
61 |
62 | @Test
63 | public void lastSavedPathOrNullIfNeverSaved() throws IOException {
64 | assertNull("should be null before first save", model.lastSavedPathOrNullIfNeverSaved());
65 | model.save(new File("blargh"));
66 | assertEquals("last save path", new File("blargh"), model.lastSavedPathOrNullIfNeverSaved());
67 | }
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/src/com/jamesshore/finances/persistence/UserConfiguration.java:
--------------------------------------------------------------------------------
1 | package com.jamesshore.finances.persistence;
2 |
3 | import java.io.*;
4 | import com.jamesshore.finances.util.*;
5 | import com.jamesshore.finances.values.*;
6 |
7 | public class UserConfiguration {
8 |
9 | public static boolean STUB_OUT_FILE_SYSTEM_FOR_TESTING_ONLY = false;
10 |
11 | public static final UserEnteredDollars DEFAULT_STARTING_BALANCE = new UserEnteredDollars("10000");
12 | public static final UserEnteredDollars DEFAULT_STARTING_COST_BASIS = new UserEnteredDollars("7000");
13 | public static final UserEnteredDollars DEFAULT_YEARLY_SPENDING = new UserEnteredDollars("695");
14 |
15 | private Observer observer = null;
16 | private UserEnteredDollars startingBalance = DEFAULT_STARTING_BALANCE;
17 | private UserEnteredDollars startingCostBasis = DEFAULT_STARTING_COST_BASIS;
18 | private UserEnteredDollars yearlySpending = DEFAULT_YEARLY_SPENDING;
19 | private File path = null;
20 |
21 | public File lastSavedPathOrNullIfNeverSaved() {
22 | return path;
23 | }
24 |
25 | public void save(File path) throws IOException {
26 | writeFile(path);
27 | this.path = path;
28 | }
29 |
30 | private void writeFile(File path) throws IOException {
31 | if (STUB_OUT_FILE_SYSTEM_FOR_TESTING_ONLY) return;
32 |
33 | Writer writer = new BufferedWriter(new FileWriter(path));
34 | try {
35 | writeLine(writer, "com.jamesshore.finances,1");
36 | writeLine(writer, getStartingBalance().getUserText());
37 | writeLine(writer, getStartingCostBasis().getUserText());
38 | writeLine(writer, getYearlySpending().getUserText());
39 | }
40 | finally {
41 | writer.close();
42 | }
43 | }
44 |
45 | private void writeLine(Writer writer, String line) throws IOException {
46 | line = line.replace("\\", "\\\\");
47 | line = line.replace("\n", "\\n");
48 | writer.write(line + "\n");
49 | }
50 |
51 | public void setStartingBalance(UserEnteredDollars startingBalance) {
52 | this.startingBalance = startingBalance;
53 | notifyObserver();
54 | }
55 |
56 | public UserEnteredDollars getStartingBalance() {
57 | return startingBalance;
58 | }
59 |
60 | public void setStartingCostBasis(UserEnteredDollars startingCostBasis) {
61 | this.startingCostBasis = startingCostBasis;
62 | notifyObserver();
63 | }
64 |
65 | public UserEnteredDollars getStartingCostBasis() {
66 | return startingCostBasis;
67 | }
68 |
69 | public void setYearlySpending(UserEnteredDollars yearlySpending) {
70 | this.yearlySpending = yearlySpending;
71 | notifyObserver();
72 | }
73 |
74 | public UserEnteredDollars getYearlySpending() {
75 | return yearlySpending;
76 | }
77 |
78 | public void setObserver(Observer observer) {
79 | Require.that(this.observer == null, "Tried to set a second observer; only one is supported so far");
80 | this.observer = observer;
81 | }
82 |
83 | private void notifyObserver() {
84 | if (observer != null) observer.configurationUpdated();
85 | }
86 |
87 | public interface Observer {
88 | public void configurationUpdated();
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/com/jamesshore/finances/values/UserEnteredDollars.java:
--------------------------------------------------------------------------------
1 | package com.jamesshore.finances.values;
2 |
3 | import com.jamesshore.finances.ui.*;
4 |
5 | public class UserEnteredDollars extends Dollars {
6 |
7 | private String userText;
8 | private Dollars backingDollars;
9 |
10 | public UserEnteredDollars(String userText) {
11 | this.userText = userText;
12 | this.backingDollars = this.parse(userText);
13 | }
14 |
15 | private Dollars parse(String text) {
16 | if (text.equals(")")) return new InvalidDollars();
17 | if (text.contains("a")) return new InvalidDollars();
18 | if (text.endsWith("d")) return new InvalidDollars();
19 | if (text.contains("e")) return new InvalidDollars();
20 | if (text.endsWith("f")) return new InvalidDollars();
21 |
22 | boolean parenthesis = false;
23 | if (text.startsWith("(")) {
24 | text = text.substring(1);
25 | parenthesis = true;
26 | }
27 | if (text.endsWith(")")) {
28 | text = text.substring(0, text.length() - 1);
29 | parenthesis = true;
30 | }
31 | if (parenthesis) text = "-" + text;
32 |
33 | if (text.startsWith("$")) text = text.substring(1);
34 | if (text.startsWith("-$")) text = "-" + text.substring(2);
35 | if (text.isEmpty()) return new ValidDollars(0);
36 | if (text.equals("-")) return new ValidDollars(0);
37 | text = text.replace(",", "");
38 |
39 | try {
40 | return Dollars.create(Double.parseDouble(text));
41 | }
42 | catch (NumberFormatException e) {
43 | return new InvalidDollars();
44 | }
45 | }
46 |
47 | @Override
48 | public boolean isValid() {
49 | return backingDollars.isValid();
50 | }
51 |
52 | @Override
53 | protected double toCoreDataType() {
54 | return backingDollars.toCoreDataType();
55 | }
56 |
57 | @Override
58 | public Dollars plus(Dollars operand) {
59 | return backingDollars.plus(operand);
60 | }
61 |
62 | @Override
63 | public Dollars minus(Dollars operand) {
64 | return backingDollars.minus(operand);
65 | }
66 |
67 | @Override
68 | public Dollars subtractToZero(Dollars operand) {
69 | return backingDollars.subtractToZero(operand);
70 | }
71 |
72 | @Override
73 | public Dollars percentage(double percent) {
74 | return backingDollars.percentage(percent);
75 | }
76 |
77 | @Override
78 | public Dollars min(Dollars operand) {
79 | return backingDollars.min(operand);
80 | }
81 |
82 | @Override
83 | public void render(Resources resources, RenderTarget target) {
84 | backingDollars.render(resources, target);
85 | }
86 |
87 | public String getUserText() {
88 | return userText;
89 | }
90 |
91 | @Override
92 | public String toString() {
93 | return backingDollars.toString();
94 | }
95 |
96 | @Override
97 | public int hashCode() {
98 | return backingDollars.hashCode();
99 | }
100 |
101 | @Override
102 | public boolean equals(Object obj) {
103 | if (obj == null) return false;
104 |
105 | Dollars that = (Dollars)obj;
106 | return this.backingDollars.equals(that);
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/src/com/jamesshore/finances/values/ValidDollars.java:
--------------------------------------------------------------------------------
1 | package com.jamesshore.finances.values;
2 |
3 | import java.awt.*;
4 | import java.text.*;
5 | import java.util.*;
6 | import com.jamesshore.finances.ui.*;
7 | import com.jamesshore.finances.util.*;
8 |
9 | public class ValidDollars extends Dollars {
10 |
11 | private double amount;
12 |
13 | public ValidDollars(double rangeLimitedAmount) {
14 | Require.that(inRange(rangeLimitedAmount), "dollar amount [" + rangeLimitedAmount + "] outside valid range");
15 | this.amount = rangeLimitedAmount;
16 | }
17 |
18 | public boolean isValid() {
19 | return true;
20 | }
21 |
22 | @Override
23 | protected double toCoreDataType() {
24 | return amount;
25 | }
26 |
27 | public Dollars plus(Dollars operand) {
28 | if (!operand.isValid()) return new InvalidDollars();
29 | return create(this.amount + operand.toCoreDataType());
30 | }
31 |
32 | public Dollars minus(Dollars operand) {
33 | if (!operand.isValid()) return new InvalidDollars();
34 | return create(this.amount - operand.toCoreDataType());
35 | }
36 |
37 | public Dollars subtractToZero(Dollars operand) {
38 | if (!operand.isValid()) return new InvalidDollars();
39 | double result = this.amount - operand.toCoreDataType();
40 | return create(Math.max(0, result));
41 | }
42 |
43 | public Dollars percentage(double percent) {
44 | return create(amount * percent / 100.0);
45 | }
46 |
47 | public Dollars min(Dollars operand) {
48 | if (!operand.isValid()) return new InvalidDollars();
49 | return create(Math.min(this.amount, operand.toCoreDataType()));
50 | }
51 |
52 | private boolean isNegative() {
53 | return amount < 0;
54 | }
55 |
56 | private long roundOffPennies() {
57 | return roundOffPennies(this.amount);
58 | }
59 |
60 | private long roundOffPennies(double amount) {
61 | return Math.round(amount);
62 | }
63 |
64 | public void render(Resources resources, RenderTarget target) {
65 | target.setText(this.toString());
66 | target.setIcon(null, null);
67 | target.setForegroundColor(Color.BLACK);
68 | if (amount <= -1) target.setForegroundColor(Color.RED);
69 | }
70 |
71 | @Override
72 | public String toString() {
73 | if (amount <= -1) { // TODO: resolve duplication with render()
74 | return "(" + toAbsoluteValueString() + ")";
75 | }
76 | else {
77 | return toAbsoluteValueString();
78 | }
79 | }
80 |
81 | private String toAbsoluteValueString() {
82 | long roundedAmount = roundOffPennies();
83 | roundedAmount = Math.abs(roundedAmount);
84 | return "$" + NumberFormat.getInstance(Locale.US).format(roundedAmount);
85 | }
86 |
87 | @Override
88 | public int hashCode() {
89 | return (int)roundOffPennies();
90 | }
91 |
92 | @Override
93 | public boolean equals(Object obj) {
94 | if (obj == null) return false;
95 | Dollars that = (Dollars)obj;
96 |
97 | if (!that.isValid()) return false;
98 | return roundOffPennies(this.toCoreDataType()) == roundOffPennies(that.toCoreDataType());
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/src/com/jamesshore/finances/ui/ApplicationFrame.java:
--------------------------------------------------------------------------------
1 | package com.jamesshore.finances.ui;
2 |
3 | import java.awt.*;
4 | import java.awt.event.*;
5 | import javax.swing.*;
6 |
7 | public class ApplicationFrame extends JFrame {
8 | private static final long serialVersionUID = 1L;
9 |
10 | public static final String TITLE = "Financial Projector";
11 | public static final Point INITIAL_POSITION = new Point(400, 300);
12 | public static final Dimension INITIAL_SIZE = new Dimension(900, 400);
13 |
14 | private ApplicationModel model;
15 | private SaveAsDialog saveAsDialog;
16 |
17 | public static void newWindow() {
18 | new ApplicationFrame(new ApplicationModel()).setVisible(true);
19 | }
20 |
21 | public ApplicationFrame(ApplicationModel applicationModel) {
22 | super(TITLE);
23 | this.model = applicationModel;
24 | configureWindow();
25 | addComponents();
26 | }
27 |
28 | private void configureWindow() {
29 | setDefaultCloseOperation(DISPOSE_ON_CLOSE);
30 | setLocation(INITIAL_POSITION);
31 | setSize(INITIAL_SIZE);
32 | }
33 |
34 | private void addComponents() {
35 | Container contentPane = getContentPane();
36 | contentPane.setLayout(new BorderLayout());
37 | contentPane.add(BorderLayout.CENTER, forecastTable());
38 | contentPane.add(BorderLayout.NORTH, configurationPanel());
39 |
40 | setJMenuBar(menuBar());
41 | saveAsDialog = new SaveAsDialog(ApplicationFrame.this, model);
42 | }
43 |
44 | private Component forecastTable() {
45 | return new JScrollPane(new ForecastTable(model.stockMarketTableModel()));
46 | }
47 |
48 | private ConfigurationPanel configurationPanel() {
49 | return new ConfigurationPanel(model.userConfiguration());
50 | }
51 |
52 | private JMenuBar menuBar() {
53 | JMenuBar menuBar = new JMenuBar();
54 | JMenu fileMenu = new JMenu("File");
55 | fileMenu.add(newMenuItem());
56 | fileMenu.add(closeMenuItem());
57 | fileMenu.add(saveAsMenuItem());
58 | menuBar.add(fileMenu);
59 | return menuBar;
60 | }
61 |
62 | private JMenuItem newMenuItem() {
63 | return menuItem("New", KeyStroke.getKeyStroke(KeyEvent.VK_N, InputEvent.META_MASK), new ActionListener() {
64 | @Override
65 | public void actionPerformed(ActionEvent e) {
66 | newWindow();
67 | }
68 | });
69 | }
70 |
71 | private JMenuItem closeMenuItem() {
72 | return menuItem("Close", KeyStroke.getKeyStroke(KeyEvent.VK_W, InputEvent.META_MASK), new ActionListener() {
73 | @Override
74 | public void actionPerformed(ActionEvent e) {
75 | dispose();
76 | }
77 | });
78 | }
79 |
80 | private JMenuItem saveAsMenuItem() {
81 | return menuItem("Save As...", KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.SHIFT_MASK | InputEvent.META_MASK), new ActionListener() {
82 | @Override
83 | public void actionPerformed(ActionEvent e) {
84 | saveAsDialog.displayModally();
85 | }
86 | });
87 | }
88 |
89 | private JMenuItem menuItem(String name, KeyStroke accelerator, ActionListener action) {
90 | JMenuItem newMenuItem = new JMenuItem(name);
91 | newMenuItem.setAccelerator(accelerator);
92 | newMenuItem.addActionListener(action);
93 | return newMenuItem;
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/com/jamesshore/spikes/formatted_text/FormattedTextSpike.java:
--------------------------------------------------------------------------------
1 | package com.jamesshore.spikes.formatted_text;
2 |
3 | import java.awt.*;
4 | import java.beans.*;
5 | import java.text.*;
6 | import java.util.*;
7 | import javax.swing.*;
8 | import javax.swing.JFormattedTextField.AbstractFormatter;
9 | import javax.swing.JFormattedTextField.AbstractFormatterFactory;
10 | import javax.swing.event.*;
11 | import javax.swing.text.*;
12 |
13 | public class FormattedTextSpike extends JFrame {
14 | private static final long serialVersionUID = 1L;
15 | private JButton button = new JButton("Button");
16 |
17 | public FormattedTextSpike() {
18 | setTitle("Formatted Text Spike");
19 | setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
20 | addComponents();
21 | pack();
22 | this.setLocation(400, 200);
23 | }
24 |
25 | private void addComponents() {
26 | Container content = this.getContentPane();
27 | content.add("Center", textField());
28 | content.add("South", button);
29 | }
30 |
31 | private Component textField() {
32 | final JFormattedTextField field = new JFormattedTextField(formatterFactory());
33 | field.setValue(new Long(30));
34 | field.addPropertyChangeListener("value", new PropertyChangeListener() {
35 | @Override
36 | public void propertyChange(PropertyChangeEvent evt) {
37 | System.out.println("propertyChange event fired");
38 | Long value = (Long)evt.getNewValue();
39 | SpikeDollars dollars = new SpikeDollars(value.doubleValue());
40 | button.setText(dollars.toString());
41 | }
42 | });
43 |
44 | field.getDocument().addDocumentListener(new DocumentListener() {
45 | @Override public void removeUpdate(DocumentEvent e) { updateApplicationModel(); }
46 | @Override public void insertUpdate(DocumentEvent e) { updateApplicationModel(); }
47 | @Override public void changedUpdate(DocumentEvent e) { updateApplicationModel(); }
48 | private void updateApplicationModel() {
49 | // NOTE: field.getValue() is not updated until the field loses focus
50 | // or the user presses enter. It does not update when the document changes.
51 | System.out.println("Document event fired");
52 | Long value = (Long)field.getValue();
53 | SpikeDollars dollars = new SpikeDollars(value.doubleValue());
54 | button.setText("*" + dollars.toString());
55 | }
56 | });
57 |
58 | return field;
59 | }
60 |
61 | private AbstractFormatterFactory formatterFactory() {
62 | NumberFormat displayFormat = NumberFormat.getCurrencyInstance(Locale.FRANCE);
63 | displayFormat.setCurrency(Currency.getInstance(Locale.US));
64 | if (displayFormat instanceof DecimalFormat) {
65 | displayFormat.setMaximumFractionDigits(0);
66 | }
67 |
68 | AbstractFormatter displayFormatter = new NumberFormatter(displayFormat);
69 | AbstractFormatter editFormatter = new NumberFormatter(NumberFormat.getNumberInstance());
70 |
71 | DefaultFormatterFactory factory = new DefaultFormatterFactory(displayFormatter, displayFormatter, editFormatter);
72 |
73 | return factory;
74 | }
75 |
76 | public static void main(String[] args) {
77 | new FormattedTextSpike().setVisible(true);
78 | }
79 |
80 | }
81 |
--------------------------------------------------------------------------------
/src/com/jamesshore/spikes/icon_in_table/IconInTableSpike.java:
--------------------------------------------------------------------------------
1 | package com.jamesshore.spikes.icon_in_table;
2 |
3 | import java.awt.*;
4 | import java.net.*;
5 | import javax.swing.*;
6 | import javax.swing.table.*;
7 |
8 | public class IconInTableSpike extends JFrame {
9 | private static final long serialVersionUID = 1L;
10 | public static final Point INITIAL_POSITION = new Point(400, 300);
11 | public static final Dimension INITIAL_SIZE = new Dimension(900, 400);
12 |
13 | private DefaultTableModel tableModel;
14 |
15 | public IconInTableSpike() {
16 | setTitle("Icon In Table");
17 | setSize(INITIAL_SIZE);
18 | setLocation(INITIAL_POSITION);
19 | setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
20 | addComponents();
21 | }
22 |
23 | private void addComponents() {
24 | Container content = this.getContentPane();
25 | content.add("Center", table());
26 | }
27 |
28 | @SuppressWarnings("serial")
29 | private Component table() {
30 | String[] titles = { "Year", "Starting Balance", "Starting Principal", "Withdrawals", "Appreciation", "Deposits", "Ending Balance" };
31 | tableModel = new DefaultTableModel(titles, 0) {
32 | public Class> getColumnClass(int columnIndex) {
33 | if (columnIndex == 1) return SpikeDollars.class;
34 | else return super.getColumnClass(columnIndex);
35 | }
36 | };
37 | for (int i = 0; i < 12800; i++) {
38 | tableModel.addRow(new Integer[] { 1900 + i, -1, 8000 + i, 50 + i, 905 + i, 2000 + i, 12000 + i });
39 | tableModel.setValueAt(new SpikeDollars(10000 + i), i, 1);
40 | }
41 |
42 | JTable table = newTable();
43 | // TableColumn column = table.getColumnModel().getColumn(1);
44 | // column.setCellRenderer(renderer());
45 | table.setDefaultRenderer(SpikeDollars.class, renderer());
46 |
47 | JScrollPane scrollPane = new JScrollPane(table);
48 | return scrollPane;
49 | }
50 |
51 | @SuppressWarnings("serial")
52 | private JTable newTable() {
53 | return new JTable(tableModel) {
54 | public Component prepareRenderer(TableCellRenderer renderer, int row, int column) {
55 | Component component = super.prepareRenderer(renderer, row, column);
56 | if (isCellSelected(row, column)) return component;
57 |
58 | Color background = row % 2 == 0 ? new Color(223, 230, 236) : Color.white;
59 | component.setBackground(background);
60 | return component;
61 | }
62 | };
63 | }
64 |
65 | @SuppressWarnings("serial")
66 | private TableCellRenderer renderer() {
67 | return new DefaultTableCellRenderer() {
68 | public void setValue(Object value) {
69 | SpikeDollars dollars = (SpikeDollars)value;
70 |
71 | setForeground(Color.BLACK);
72 | setText(null);
73 | setIcon(null);
74 | setToolTipText(dollars.toString());
75 |
76 | if (dollars.amount < 10005) {
77 | setText("<");
78 | this.setForeground(Color.RED);
79 | }
80 | else if (dollars.amount > 10005) {
81 | setText(">");
82 | }
83 | else {
84 | URL iconUrl = getClass().getResource("star.gif");
85 | setIcon(new ImageIcon(iconUrl));
86 | setToolTipText("WINNER!");
87 | }
88 | }
89 | };
90 | }
91 |
92 | public static void main(String[] args) {
93 | new IconInTableSpike().setVisible(true);
94 | }
95 |
96 | }
97 |
--------------------------------------------------------------------------------
/src/com/jamesshore/finances/ui/_ConfigurationPanelTest.java:
--------------------------------------------------------------------------------
1 | package com.jamesshore.finances.ui;
2 |
3 | import static org.junit.Assert.*;
4 | import java.awt.*;
5 | import javax.swing.*;
6 | import net.miginfocom.swing.*;
7 | import org.junit.*;
8 | import com.jamesshore.finances.persistence.*;
9 | import com.jamesshore.finances.values.*;
10 |
11 | public class _ConfigurationPanelTest {
12 |
13 | private ConfigurationPanel panel;
14 | private UserConfiguration userConfiguration;
15 |
16 | private DollarsTextField startingBalanceField() {
17 | return (DollarsTextField)panel.getComponent(1);
18 | }
19 |
20 | private DollarsTextField costBasisField() {
21 | return (DollarsTextField)panel.getComponent(3);
22 | }
23 |
24 | private DollarsTextField yearlySpendingField() {
25 | return (DollarsTextField)panel.getComponent(5);
26 | }
27 |
28 | @Before
29 | public void setUp() {
30 | ApplicationModel model = new ApplicationModel();
31 | userConfiguration = model.userConfiguration();
32 | panel = new ConfigurationPanel(userConfiguration);
33 | }
34 |
35 | @Test
36 | public void layout() {
37 | MigLayout manager = (MigLayout)panel.getLayout();
38 | assertEquals("layout", MigLayout.class, manager.getClass());
39 | assertEquals("layout constraints", "fillx, wrap 2", manager.getLayoutConstraints());
40 | assertEquals("column constraints", "[right]rel[grow]", manager.getColumnConstraints());
41 |
42 | Component[] components = panel.getComponents();
43 | assertEquals("# of components", 6, components.length);
44 | assertFormField("starting balance", components[0], components[1]);
45 | assertFormField("cost basis", components[2], components[3]);
46 | assertFormField("yearly spending", components[4], components[5]);
47 | }
48 |
49 | private void assertFormField(String message, Component label, Component field) {
50 | MigLayout manager = (MigLayout)panel.getLayout();
51 | assertEquals(message + " label", JLabel.class, label.getClass());
52 | assertEquals(message + " field", DollarsTextField.class, field.getClass());
53 | assertEquals(message + " field constraint", "growx", manager.getComponentConstraints(field));
54 | }
55 |
56 | @Test
57 | public void fieldsInitializeToModelValue() {
58 | assertEquals("starting balance field text", userConfiguration.getStartingBalance(), startingBalanceField().getDollars());
59 | assertEquals("cost basis field text", userConfiguration.getStartingCostBasis(), costBasisField().getDollars());
60 | assertEquals("yearly spending field text", userConfiguration.getYearlySpending(), yearlySpendingField().getDollars());
61 | }
62 |
63 | @Test
64 | public void startingBalanceFieldUpdatesModel() {
65 | startingBalanceField().setText("668");
66 | assertEquals("user configuration should be updated", new ValidDollars(668), userConfiguration.getStartingBalance());
67 | }
68 |
69 | @Test
70 | public void costBasisFieldUpdatesModel() {
71 | costBasisField().setText("670");
72 | assertEquals("user configuration should be updated", new ValidDollars(670), userConfiguration.getStartingCostBasis());
73 | }
74 |
75 | @Test
76 | public void yearlySpendingFieldUpdatesModel() {
77 | yearlySpendingField().setText("672");
78 | assertEquals("user configuration should be updated", new ValidDollars(672), userConfiguration.getYearlySpending());
79 | }
80 |
81 | }
82 |
--------------------------------------------------------------------------------
/scratchpad.txt:
--------------------------------------------------------------------------------
1 | == Finish basic saving ==
2 |
3 | Create smoke test to make sure TESTING_ONLY variable isn't flipped in production?
4 |
5 | Handle SaveFile stub-out-file-system cheat more generically (duplicated between ApplicationModelTest & SaveAsDialogTest)
6 |
7 |
8 | == Special cases / polish ==
9 |
10 | Exploratory testing discovery: negative zeros in UI (enter $10 starting balance, $0 cost basis, $1 yearly spending)
11 |
12 | Take a second look at IOExceptions -- are there special cases to handle?
13 |
14 | Deal with FileDialog returning invalid directory/filename. (It can happen, according to the Java docs)
15 |
16 | Deal with FileDialog returning null directory and valid filename.
17 |
18 | Application menu still works when modal dialog is up -- so you can "Save As" twice in a row, for example. Fix this.
19 |
20 | Need to decide what to do with original user-entered values in UI.
21 | - Reformat them when moving away from field? What about invalid values?
22 |
23 |
24 | == Loading ==
25 |
26 | Deal with opening same file twice (see episode 137 comments, Aleksi Niiranen)
27 |
28 | Deal with loading excessively large file
29 |
30 |
31 | File format: consider java.util.Properties (suggested by Esko Luontola in ep 159)
32 | ---
33 |
34 | Use a @Rule to enforce SwingUtilities.invokeAndWait on every Swing test?
35 |
36 | ---
37 |
38 | Persistence!
39 | - values of fields
40 | - UI positioning
41 |
42 | ---
43 |
44 | When tabbing to DollarsTextField, should the value be selected?
45 |
46 | Need to handle case where cost basis is larger than starting balance--that should be illegal
47 |
48 | Similarly, need to handle case where account runs out of money. All kinds of funkiness ensues.
49 |
50 | ---
51 |
52 | Add remaining fields so that starting conditions can be set at runtime
53 |
54 | ---
55 |
56 | Check that keyboard shortcuts work (including tabbing between components)
57 |
58 | Run Swing-related stuff on event dispatching thread: http://download.oracle.com/javase/6/docs/api/javax/swing/package-summary.html#threading
59 |
60 | Error handling for exceptions within Swing components--how?
61 |
62 | Tip for running Swing tests: http://blog.schauderhaft.de/2010/08/15/use-cases-for-junit-rules/ (via Esko Luontola)
63 |
64 | Tool for Swing testing: Consider 'WindowLicker' (suggested by Tony in email)
65 |
66 | ---
67 | Polish:
68 |
69 | - Better tooltips for invalid dollar amounts
70 |
71 | - Need a alert dialog that wraps long lines properly (for example, save error dialog)
72 |
73 | ---
74 | Long term:
75 |
76 | - Cross-platform testing & support (Consider "Macify")
77 | - Cmd-W should close window on Mac
78 | - Does the DollarsTextField warning icon work properly in all look-and-feels that we use
79 | - Mac needs to use Mac menu bar (currently configured with -D option in run.sh)
80 | - Should not close application when last window is closed on the Mac
81 | - More realistic abstraction for capital gains tax (in StockMarketYear)
82 |
83 |
84 | ROOT CAUSE CHECKLIST:
85 | ---
86 | Swing semantics change even when interface doesn't
87 | Therefore... when refactoring from one Swing class to another, check usage of all Swing APIs.
88 |
89 | When changing a test, you could break the expected behavior of the application
90 | Therefore... go a little slower when taking out or changing expectations of tests
91 |
--------------------------------------------------------------------------------
/src/com/jamesshore/finances/ui/_SaveAsDialogTest.java:
--------------------------------------------------------------------------------
1 | package com.jamesshore.finances.ui;
2 |
3 | import static org.junit.Assert.*;
4 | import java.awt.*;
5 | import java.io.*;
6 | import javax.swing.*;
7 | import org.junit.*;
8 | import com.jamesshore.finances.persistence.*;
9 |
10 | public class _SaveAsDialogTest {
11 |
12 | private ApplicationModel model = new ApplicationModel();
13 | private SaveAsDialog dialog = new SaveAsDialog(null, model);
14 |
15 | @Before
16 | public void setup() {
17 | UserConfiguration.STUB_OUT_FILE_SYSTEM_FOR_TESTING_ONLY = true;
18 | }
19 |
20 | @After
21 | public void teardown() {
22 | UserConfiguration.STUB_OUT_FILE_SYSTEM_FOR_TESTING_ONLY = false;
23 | dialog.dispose();
24 | }
25 |
26 | @Test
27 | public void layout() {
28 | assertEquals("Save As dialog mode should be 'save'", FileDialog.SAVE, dialog.getMode());
29 | assertEquals("Save As dialog title", "Save As", dialog.getTitle());
30 | }
31 |
32 | @Test
33 | public void saveAsDialogShouldTellApplicationModelToSaveWhenSaveButtonPushed() {
34 | doSave(dialog, "/example", "filename");
35 | assertEquals("applicationModel should be told to save", new File("/example/filename"), model.lastSavedPathOrNullIfNeverSaved());
36 | }
37 |
38 | @Test
39 | public void saveAsDialogShouldDoNothingWhenCancelButtonPushed() {
40 | doSave(dialog, null, null);
41 | assertNull("applicationModel should not have been told to save", model.lastSavedPathOrNullIfNeverSaved());
42 | }
43 |
44 | @Test
45 | public void saveAsDialogShouldHandleSaveExceptionsGracefully() {
46 | final Frame frame = new Frame();
47 | final SaveAsDialog exceptionThrowingDialog = createExceptionThrowingSaveAsDialog(frame);
48 |
49 | __Invocation.invokeAndWaitFor("warning dialog", 1000, new __Invocation() {
50 | @Override
51 | public void invoke() {
52 | doSave(exceptionThrowingDialog, "/example", "filename");
53 | }
54 |
55 | @Override
56 | public boolean stopWaitingWhen() {
57 | Dialog dialog = warningDialogOrNullIfNotFound(frame);
58 | return dialog != null && dialog.isVisible();
59 | }
60 | });
61 |
62 | JDialog dialogWindow = (JDialog)warningDialogOrNullIfNotFound(frame);
63 | JOptionPane dialogPane = (JOptionPane)dialogWindow.getContentPane().getComponent(0);
64 | assertEquals("Warning dialog parent", frame, dialogWindow.getParent());
65 | assertEquals("Warning dialog title", "Save File", dialogWindow.getTitle());
66 | assertEquals("Warning dialog message", "Could not save file: generic exception", dialogPane.getMessage());
67 | assertEquals("Warning dialog type should be 'warning'", JOptionPane.WARNING_MESSAGE, dialogPane.getMessageType());
68 | }
69 |
70 | private SaveAsDialog createExceptionThrowingSaveAsDialog(Frame frame) {
71 | final ApplicationModel exceptionThrower = new ApplicationModel() {
72 | @Override
73 | public void save(File saveFile) throws IOException {
74 | throw new IOException("generic exception");
75 | }
76 | };
77 |
78 | return new SaveAsDialog(frame, exceptionThrower);
79 | }
80 |
81 | private Dialog warningDialogOrNullIfNotFound(Frame frame) {
82 | Window[] childWindows = frame.getOwnedWindows();
83 | if (childWindows.length < 2) return null;
84 | return (Dialog)childWindows[1];
85 | }
86 |
87 | private void doSave(SaveAsDialog exceptionThrowingDialog, String directory, String filename) {
88 | exceptionThrowingDialog.setDirectory(directory);
89 | exceptionThrowingDialog.setFile(filename);
90 | exceptionThrowingDialog.doSave();
91 | }
92 |
93 | }
94 |
--------------------------------------------------------------------------------
/src/com/jamesshore/spikes/icon_in_textfield/IconInTextField.java:
--------------------------------------------------------------------------------
1 | package com.jamesshore.spikes.icon_in_textfield;
2 |
3 | import java.awt.*;
4 | import java.awt.event.*;
5 | import java.net.*;
6 | import javax.swing.*;
7 |
8 | public class IconInTextField extends JFrame {
9 | private static final long serialVersionUID = 1L;
10 |
11 | private JLabel iconLabel;
12 |
13 | public IconInTextField() {
14 | setTitle("Icon in Textfield Spike");
15 | setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
16 | addComponents();
17 | pack();
18 | this.setLocation(400, 200);
19 | }
20 |
21 | private void addComponents() {
22 | Container content = this.getContentPane();
23 | // content.add("Center", iconTextFieldWithGridBag());
24 | content.add("Center", iconTextFieldWithOverlayLayout());
25 | content.add("South", button());
26 | // content.add("Center", textField());
27 | // content.add("East", icon());
28 | }
29 |
30 | private Component iconTextFieldWithOverlayLayout() {
31 | JPanel panel = new JPanel();
32 | panel.setLayout(new OverlayLayout(panel));
33 |
34 | JPanel iconPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
35 | iconPanel.setOpaque(false);
36 | iconPanel.add(icon());
37 |
38 | JPanel textPanel = new JPanel(new BorderLayout());
39 | textPanel.add(textField(), BorderLayout.NORTH);
40 | textPanel.setOpaque(false);
41 |
42 | // JPanel textPanel = new JPanel();
43 | // textPanel.setLayout(new GridBagLayout());
44 | // textPanel.add(textField(), new GridBagConstraints(0, 0, 1, 1, 1, 1, GridBagConstraints.NORTH,
45 | // GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0));
46 |
47 | panel.add(iconPanel);
48 | panel.add(textPanel);
49 | return panel;
50 | }
51 |
52 | @SuppressWarnings("unused")
53 | private Component iconTextFieldWithSideBySideLayout() {
54 | JPanel gridbag = new JPanel();
55 | gridbag.setLayout(new GridBagLayout());
56 |
57 | gridbag.add(textField(), new GridBagConstraints(0, 0, 1, 1, 1, 1, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0));
58 | gridbag.add(icon(), new GridBagConstraints(1, 0, 1, 1, 0, 1, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0));
59 |
60 | return gridbag;
61 | // Box box = Box.createVerticalBox();
62 | // box.add(textField());
63 | // JPanel borderPanel = new JPanel();
64 | // borderPanel.setLayout(new BorderLayout());
65 | // borderPanel.add("Center", textField());
66 | // borderPanel.add("East", icon());
67 | // box.add(borderPanel);
68 | // box.add(Box.createVerticalGlue());
69 | // return box;
70 | }
71 |
72 | private Component textField() {
73 | final JTextField field = new JTextField();
74 | field.setText("foodle");
75 | return field;
76 | }
77 |
78 | private JLabel icon() {
79 | iconLabel = new JLabel();
80 | URL iconUrl = getClass().getResource("invalid_dollars.png");
81 | ImageIcon icon = new ImageIcon(iconUrl, "Invalid dollar amount");
82 | iconLabel.setIcon(icon);
83 | iconLabel.setBackground(Color.WHITE);
84 | iconLabel.setOpaque(false);
85 |
86 | return iconLabel;
87 | }
88 |
89 | private Component button() {
90 | final JButton button = new JButton("Toggle Warning");
91 | button.addActionListener(new ActionListener() {
92 | public void actionPerformed(ActionEvent e) {
93 | iconLabel.setVisible(!iconLabel.isVisible());
94 | }
95 | });
96 | return button;
97 | }
98 |
99 | public static void main(String[] args) {
100 | new IconInTextField().setVisible(true);
101 | }
102 |
103 | }
104 |
--------------------------------------------------------------------------------
/src/com/jamesshore/spikes/swt/SwtSpike.java:
--------------------------------------------------------------------------------
1 | // This file commented out so everything would compile without SWT being needed in the build path.
2 | // Having SWT in the build path prevents Swing from loading properly, which messes up the tests.
3 |
4 | //package com.jamesshore.spikes.swt;
5 | //
6 | //import org.eclipse.swt.*;
7 | //import org.eclipse.swt.events.*;
8 | //import org.eclipse.swt.layout.*;
9 | //import org.eclipse.swt.widgets.*;
10 | //
11 | //public class SwtSpike {
12 | //
13 | // public static void main (String[] args) {
14 | // Display display = new Display();
15 | // Shell shell = new Shell(display);
16 | // layout(shell);
17 | // shell.pack();
18 | // shell.open();
19 | // while (!shell.isDisposed()) {
20 | // if (!display.readAndDispatch()) display.sleep();
21 | // }
22 | // display.dispose();
23 | // }
24 | //
25 | // private static void layout(final Shell shell) {
26 | // shell.setLayout(new RowLayout(SWT.VERTICAL));
27 | //
28 | // createMenu(shell);
29 | // Table table = createTable(shell);
30 | // createButton(shell, table);
31 | // }
32 | //
33 | // private static void createMenu(final Shell shell) {
34 | // Menu bar = new Menu(shell, SWT.BAR);
35 | // shell.setMenuBar(bar);
36 | // MenuItem fileItem = new MenuItem(bar, SWT.CASCADE);
37 | // fileItem.setText("&File");
38 | // Menu fileMenu = new Menu(shell, SWT.DROP_DOWN);
39 | // fileItem.setMenu(fileMenu);
40 | // MenuItem close = new MenuItem(fileMenu, SWT.PUSH);
41 | // close.setText("Close");
42 | // close.setAccelerator(SWT.MOD1 + 'W');
43 | // close.addListener(SWT.Selection, new Listener() {
44 | // public void handleEvent(Event e) {
45 | // shell.close();
46 | // }
47 | // });
48 | // }
49 | //
50 | // private static Table createTable(final Shell shell) {
51 | // Table table = new Table(shell, SWT.MULTI | SWT.BORDER | SWT.FULL_SELECTION | SWT.SCROLL_LINE);
52 | // table.setLinesVisible(true);
53 | // table.setHeaderVisible(true);
54 | //
55 | // RowData data = new RowData(SWT.DEFAULT, 500);
56 | // table.setLayoutData(data);
57 | //
58 | // String[] titles = {"Year", "Starting Balance", "Starting Principal", "Withdrawals", "Appreciation", "Deposits", "Ending Balance"};
59 | // for (String title : titles) {
60 | // TableColumn column = new TableColumn(table, SWT.NONE);
61 | // column.setText(title);
62 | // }
63 | //
64 | // for (int i = 0; i < 12800; i++) {
65 | // TableItem item = new TableItem(table, SWT.NONE);
66 | // item.setText(0, "" + (1900 + i));
67 | // item.setText(1, "" + (10000 + i));
68 | // item.setText(2, "" + (8000 + i));
69 | // item.setText(3, "" + (50 + i));
70 | // item.setText(4, "" + (905 + i));
71 | // item.setText(5, "" + (2000 + i));
72 | // item.setText(6, "" + (12000 + i));
73 | // }
74 | // for (int i = 0; i < titles.length; i++) {
75 | // table.getColumn(i).pack();
76 | // }
77 | //
78 | // return table;
79 | // }
80 | //
81 | // private static void createButton(final Shell shell, final Table table) {
82 | // final Button button = new Button(shell, SWT.PUSH);
83 | // button.setText("I'm a button!");
84 | // button.addSelectionListener(new SelectionAdapter() {
85 | // public void widgetSelected(SelectionEvent e) {
86 | // button.setText("The world will end in " + (Math.random() * 100000) + " seconds");
87 | // for (TableItem row : table.getItems()) {
88 | // int currentValue = Integer.parseInt(row.getText(1));
89 | // row.setText(1, "" + (int)(currentValue * 1.03));
90 | // }
91 | // for (int i = 0; i < 7; i++) {
92 | // table.getColumn(i).pack();
93 | // }
94 | // shell.pack();
95 | // }
96 | // });
97 | // }
98 | //
99 | // }
100 |
--------------------------------------------------------------------------------
/src/com/jamesshore/finances/values/_InvalidDollarsTest.java:
--------------------------------------------------------------------------------
1 | package com.jamesshore.finances.values;
2 |
3 | import static org.junit.Assert.*;
4 | import javax.swing.*;
5 | import org.junit.*;
6 | import com.jamesshore.finances.ui.*;
7 | import com.jamesshore.finances.util.*;
8 |
9 | public class _InvalidDollarsTest {
10 |
11 | private InvalidDollars invalidA;
12 | private InvalidDollars invalidB;
13 | private ValidDollars valid;
14 |
15 | @Before
16 | public void setup() {
17 | invalidA = new InvalidDollars();
18 | invalidB = new InvalidDollars();
19 | valid = (ValidDollars)new ValidDollars(13);
20 | }
21 |
22 | @Test
23 | public void isValid() {
24 | assertFalse(invalidA.isValid());
25 | }
26 |
27 | @Test(expected = UnreachableCodeException.class)
28 | public void toCoreDataType_IsAnError() {
29 | invalidA.toCoreDataType();
30 | }
31 |
32 | @Test
33 | public void plus() {
34 | assertEquals(new InvalidDollars(), invalidA.plus(invalidB));
35 | assertEquals(new InvalidDollars(), invalidA.plus(valid));
36 | assertEquals(new InvalidDollars(), valid.plus(invalidA));
37 | }
38 |
39 | @Test
40 | public void minus() {
41 | assertEquals(new InvalidDollars(), invalidA.minus(invalidB));
42 | assertEquals(new InvalidDollars(), invalidA.minus(valid));
43 | assertEquals(new InvalidDollars(), valid.minus(invalidB));
44 | }
45 |
46 | @Test
47 | public void subtractToZero() {
48 | assertEquals(new InvalidDollars(), invalidA.subtractToZero(invalidB));
49 | assertEquals(new InvalidDollars(), invalidA.subtractToZero(valid));
50 | assertEquals(new InvalidDollars(), valid.subtractToZero(invalidB));
51 | }
52 |
53 | @Test
54 | public void percentage() {
55 | assertEquals(new InvalidDollars(), invalidA.percentage(10));
56 | }
57 |
58 | @Test
59 | public void min() {
60 | assertEquals(new InvalidDollars(), invalidA.min(invalidB));
61 | assertEquals(new InvalidDollars(), invalidA.min(valid));
62 | assertEquals(new InvalidDollars(), valid.min(invalidB));
63 | }
64 |
65 | @Test
66 | public void flipSign() {
67 | assertEquals(new InvalidDollars(), invalidA.flipSign());
68 | }
69 |
70 | @Test
71 | public void rendersItself() {
72 | __RenderTargetStub target = new __RenderTargetStub();
73 | invalidA.render(new Resources(), target);
74 |
75 | ImageIcon expectedIcon = new Resources().invalidDollarIcon();
76 | ImageIcon actualIcon = (ImageIcon)target.icon;
77 |
78 | assertEquals("icon image", expectedIcon.getImage(), actualIcon.getImage());
79 | assertEquals("icon description", "Invalid dollar amount", actualIcon.getDescription());
80 | assertEquals("tooltip message", "Invalid dollar amount", target.toolTipText);
81 | }
82 |
83 | @Test
84 | public void renderingShouldResetLabelToDefaultState() {
85 | __RenderTargetStub target = new __RenderTargetStub();
86 | target.text = "foodle";
87 |
88 | invalidA.render(new Resources(), target);
89 | assertNull("should have no text", target.text);
90 | }
91 |
92 | @Test
93 | public void valueObject() {
94 | assertEquals("$???", invalidA.toString());
95 | assertTrue("invalid dollars should always be equal", invalidA.equals(invalidB));
96 | assertFalse("invalid dollars shouldn't equal valid dollars", invalidA.equals(valid));
97 | assertFalse("invalid dollars shouldn't equal valid user-entered dollars", invalidA.equals(new UserEnteredDollars("1")));
98 | assertTrue("invalid dollars should equal invalid user-entered dollars", invalidA.equals(new UserEnteredDollars("xxx")));
99 |
100 | assertFalse("shouldn't blow up when comparing to null", invalidA.equals(null));
101 | assertTrue("equal dollars should have same hash code", invalidA.hashCode() == invalidB.hashCode());
102 | }
103 |
104 | }
105 |
--------------------------------------------------------------------------------
/src/com/jamesshore/finances/ui/DollarsTextField.java:
--------------------------------------------------------------------------------
1 | package com.jamesshore.finances.ui;
2 |
3 | import java.awt.*;
4 | import javax.swing.*;
5 | import javax.swing.event.*;
6 | import com.jamesshore.finances.values.*;
7 |
8 | // If you want to subclass this class, it's okay to remove the 'final' designator, but be careful of race
9 | // conditions with the event handler in the constructor. It could execute before the subclass constructor.
10 | public final class DollarsTextField extends JPanel {
11 | private static final long serialVersionUID = 1L;
12 |
13 | private JTextField textField = new JTextField();
14 | private JLabel iconLabel = new JLabel();
15 |
16 | public DollarsTextField(Dollars initialValue) {
17 | textField.setText(initialValue.toString());
18 |
19 | addComponents();
20 | addListener();
21 | }
22 |
23 | private void addListener() {
24 | addTextChangeListener(new ChangeListener() {
25 | public void textChanged() {
26 | getDollars().render(new Resources(), new DollarsTextFieldRenderTargetAdapter(DollarsTextField.this));
27 | }
28 | });
29 | }
30 |
31 | private void addComponents() {
32 | JPanel iconPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
33 | iconPanel.add(iconLabel);
34 | iconPanel.setOpaque(false);
35 |
36 | this.setLayout(new OverlayLayout(this));
37 | this.add(iconPanel);
38 | this.add(textField);
39 | iconLabel.setVisible(false);
40 | }
41 |
42 | public void setIcon(Icon icon) {
43 | if (icon == null) {
44 | iconLabel.setVisible(false);
45 | }
46 | else {
47 | iconLabel.setIcon(icon);
48 | iconLabel.setVisible(true);
49 | }
50 | }
51 |
52 | public void setIconToolTipText(String text) {
53 | iconLabel.setToolTipText(text);
54 | }
55 |
56 | public String getText() {
57 | return textField.getText();
58 | }
59 |
60 | public void setText(String value) {
61 | textField.setText(value);
62 | }
63 |
64 | @Override
65 | public Color getForeground() {
66 | if (textField != null) return textField.getForeground();
67 | else return super.getForeground();
68 | }
69 |
70 | @Override
71 | public void setForeground(Color color) {
72 | super.setForeground(color);
73 | if (textField != null) textField.setForeground(color);
74 | }
75 |
76 | public UserEnteredDollars getDollars() {
77 | return new UserEnteredDollars(textField.getText());
78 | }
79 |
80 | public void addTextChangeListener(final ChangeListener listener) {
81 | textField.getDocument().addDocumentListener(new DocumentListener() {
82 | @Override
83 | public void removeUpdate(DocumentEvent e) {
84 | fireEvent();
85 | }
86 |
87 | @Override
88 | public void insertUpdate(DocumentEvent e) {
89 | fireEvent();
90 | }
91 |
92 | @Override
93 | public void changedUpdate(DocumentEvent e) {
94 | fireEvent();
95 | }
96 |
97 | private void fireEvent() {
98 | listener.textChanged();
99 | }
100 | });
101 | }
102 |
103 | public static interface ChangeListener {
104 | public void textChanged();
105 | }
106 |
107 | private static class DollarsTextFieldRenderTargetAdapter implements RenderTarget {
108 | private DollarsTextField field;
109 |
110 | public DollarsTextFieldRenderTargetAdapter(DollarsTextField field) {
111 | this.field = field;
112 | }
113 |
114 | @Override
115 | public void setText(String text) {
116 | // this space intentionally left blank. We never overwrite user's text.
117 | }
118 |
119 | @Override
120 | public void setIcon(Icon icon, String toolTipText) {
121 | field.setIcon(icon);
122 | field.setIconToolTipText(toolTipText);
123 | }
124 |
125 | @Override
126 | public void setForegroundColor(Color color) {
127 | field.setForeground(color);
128 | }
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/src/com/jamesshore/finances/ui/_ForecastTableTest.java:
--------------------------------------------------------------------------------
1 | package com.jamesshore.finances.ui;
2 |
3 | import static org.junit.Assert.*;
4 | import java.awt.*;
5 | import javax.swing.*;
6 | import javax.swing.table.*;
7 | import org.junit.*;
8 | import com.jamesshore.finances.values.*;
9 |
10 | public class _ForecastTableTest {
11 |
12 | @Test
13 | public void tableRowShouldUseStandardColor_WhenJustOneRow() {
14 | DefaultTableModel tableModel = new DefaultTableModel(0, 1);
15 | tableModel.addRow(new String[] { "" });
16 | JTable table = new ForecastTable(tableModel);
17 |
18 | assertEquals("row 0 should have standard background", ForecastTable.STANDARD_BACKGROUND_COLOR, getCellBackground(table, 0, 0));
19 | }
20 |
21 | @Test
22 | public void tableRowsShouldAlternateColors_WhenThereAreNoColumnHeaders() {
23 | DefaultTableModel tableModel = new DefaultTableModel(0, 1);
24 | tableModel.addRow(new String[] { "" });
25 | tableModel.addRow(new String[] { "" });
26 | tableModel.addRow(new String[] { "" });
27 | tableModel.addRow(new String[] { "" });
28 | JTable table = new ForecastTable(tableModel);
29 |
30 | assertEquals("row 0 should have standard background", ForecastTable.STANDARD_BACKGROUND_COLOR, getCellBackground(table, 0, 0));
31 | assertEquals("row 1 should have alternate background", ForecastTable.ALTERNATE_BACKGROUND_COLOR, getCellBackground(table, 1, 0));
32 | assertEquals("row 2 should have standard background", ForecastTable.STANDARD_BACKGROUND_COLOR, getCellBackground(table, 2, 0));
33 | assertEquals("row 3 should have alternate background", ForecastTable.ALTERNATE_BACKGROUND_COLOR, getCellBackground(table, 3, 0));
34 | }
35 |
36 | @Test
37 | public void tableRowsShouldAlternateColors_WhenThereAreColumnHeaders() {
38 | DefaultTableModel tableModel = new DefaultTableModel(0, 1);
39 | tableModel.setColumnIdentifiers(new Object[] { "Header" });
40 | tableModel.addRow(new String[] { "" });
41 | tableModel.addRow(new String[] { "" });
42 | tableModel.addRow(new String[] { "" });
43 | tableModel.addRow(new String[] { "" });
44 | JTable table = new ForecastTable(tableModel);
45 |
46 | assertEquals("row 0 should have standard background", ForecastTable.STANDARD_BACKGROUND_COLOR, getCellBackground(table, 0, 0));
47 | assertEquals("row 1 should have alternate background", ForecastTable.ALTERNATE_BACKGROUND_COLOR, getCellBackground(table, 1, 0));
48 | assertEquals("row 2 should have standard background", ForecastTable.STANDARD_BACKGROUND_COLOR, getCellBackground(table, 2, 0));
49 | assertEquals("row 3 should have alternate background", ForecastTable.ALTERNATE_BACKGROUND_COLOR, getCellBackground(table, 3, 0));
50 | }
51 |
52 | @Test
53 | public void tableRowsShouldUseSelectionBackgroundColor_WhenSelected() {
54 | DefaultTableModel tableModel = new DefaultTableModel(0, 1);
55 | tableModel.addRow(new String[] { "" });
56 | JTable table = new ForecastTable(tableModel);
57 |
58 | table.setRowSelectionInterval(0, 0);
59 | assertEquals("row 0 should have selection background", ForecastTable.SELECTION_BACKGROUND_COLOR, getCellBackground(table, 0, 0));
60 | }
61 |
62 | @Test
63 | @SuppressWarnings("serial")
64 | public void tableShouldHaveSelfRenderableObjectsRenderThemselves() {
65 | SelfRenderable renderable = new SelfRenderable() {
66 | public void render(Resources resources, RenderTarget target) {
67 | target.setText("I rendered myself");
68 | }
69 | };
70 | DefaultTableModel tableModel = new DefaultTableModel(0, 1) {
71 | public Class> getColumnClass(int column) {
72 | return SelfRenderable.class;
73 | }
74 | };
75 | tableModel.addRow(new SelfRenderable[] { renderable });
76 | JTable table = new ForecastTable(tableModel);
77 |
78 | assertEquals("I rendered myself", getCellText(table, 0, 0));
79 | }
80 |
81 | private Color getCellBackground(JTable table, int row, int column) {
82 | TableCellRenderer renderer = table.getCellRenderer(row, column);
83 | Component component = table.prepareRenderer(renderer, row, column);
84 | Color actualColor = component.getBackground();
85 | return actualColor;
86 | }
87 |
88 | private String getCellText(JTable table, int row, int column) {
89 | TableCellRenderer renderer = table.getCellRenderer(row, column);
90 | JLabel label = (JLabel)table.prepareRenderer(renderer, row, column);
91 | return label.getText();
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/src/com/jamesshore/spikes/looktest/LookSpike.java:
--------------------------------------------------------------------------------
1 | package com.jamesshore.spikes.looktest;
2 |
3 | import java.awt.*;
4 | import java.awt.event.*;
5 | import javax.swing.*;
6 | import javax.swing.table.*;
7 |
8 | public class LookSpike extends JFrame {
9 | private static final long serialVersionUID = 1L;
10 |
11 | private DefaultTableModel tableModel;
12 |
13 | public LookSpike() {
14 | setTitle("Swing Spike");
15 | setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
16 | setJMenuBar(menuBar());
17 | addComponents();
18 | pack();
19 | setLocation(300, 200);
20 | }
21 |
22 | private JMenuBar menuBar() {
23 | JMenuBar result = new JMenuBar();
24 | result.add(fileMenu());
25 |
26 | return result;
27 | }
28 |
29 | private JMenu fileMenu() {
30 | JMenu result = new JMenu("File");
31 | result.add(closeAction());
32 | return result;
33 | }
34 |
35 | private JMenuItem closeAction() {
36 | JMenuItem result = new JMenuItem("Close");
37 | result.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_W, InputEvent.META_MASK));
38 | result.addActionListener(new ActionListener() {
39 | public void actionPerformed(ActionEvent e) {
40 | LookSpike.this.dispose();
41 | }
42 | });
43 | return result;
44 | }
45 |
46 | private void addComponents() {
47 | Container content = this.getContentPane();
48 | content.add("South", button());
49 | content.add("Center", table());
50 | }
51 |
52 | private Component button() {
53 | final JButton button = new JButton("Foo!");
54 | button.addActionListener(new ActionListener() {
55 | public void actionPerformed(ActionEvent e) {
56 | button.setText("The world will end in " + (Math.random() * 100000) + " seconds");
57 | for (int i = 0; i < tableModel.getRowCount(); i++) {
58 | int cell = (Integer)tableModel.getValueAt(i, 1);
59 | tableModel.setValueAt((int)(cell * 1.03), i, 1);
60 | }
61 | }
62 | });
63 |
64 | JPanel panel = new JPanel(new FlowLayout(FlowLayout.CENTER));
65 | panel.add(button);
66 | return panel;
67 | }
68 |
69 | private Component table() {
70 | String[] titles = {"Year", "Starting Balance", "Starting Principal", "Withdrawals", "Appreciation", "Deposits", "Ending Balance"};
71 | tableModel = new DefaultTableModel(titles, 0);
72 | for (int i = 0; i < 12800; i++) {
73 | tableModel.addRow(new Integer[] {1900 + i, 10000 + i, 8000 + i, 50 + i, 905 + i, 2000 + i, 12000 + i});
74 | }
75 |
76 | JTable table = new CustomTable(tableModel);
77 |
78 | int preferredWidth = 0;
79 | for (int i = 0; i < table.getColumnCount(); i++) {
80 | int width = packColumn(table, i, 2);
81 | preferredWidth += width;
82 | }
83 | Dimension preferredSize = new Dimension(preferredWidth, 400);
84 | table.setPreferredScrollableViewportSize(preferredSize);
85 | this.setMinimumSize(preferredSize);
86 |
87 | JScrollPane scrollPane = new JScrollPane(table);
88 | return scrollPane;
89 | }
90 |
91 | // This method from http://www.exampledepot.com/egs/javax.swing.table/PackCol.html
92 | public int packColumn(JTable table, int vColIndex, int margin) {
93 | DefaultTableColumnModel colModel = (DefaultTableColumnModel) table.getColumnModel();
94 | TableColumn col = colModel.getColumn(vColIndex);
95 | int width = 0;
96 |
97 | // Get width of column header
98 | TableCellRenderer renderer = col.getHeaderRenderer();
99 | if (renderer == null) {
100 | renderer = table.getTableHeader().getDefaultRenderer();
101 | }
102 | Component comp = renderer.getTableCellRendererComponent(table, col.getHeaderValue(), false, false, 0, 0);
103 | width = comp.getPreferredSize().width;
104 |
105 | // Get maximum width of column data
106 | for (int r = 0; r < table.getRowCount(); r++) {
107 | renderer = table.getCellRenderer(r, vColIndex);
108 | comp = renderer.getTableCellRendererComponent(table, table.getValueAt(r, vColIndex), false, false, r, vColIndex);
109 | width = Math.max(width, comp.getPreferredSize().width);
110 | }
111 |
112 | // Add margin
113 | width += 2 * margin;
114 |
115 | // Set the width
116 | col.setPreferredWidth(width);
117 |
118 | return width;
119 | }
120 |
121 | public static void main(String[] args) {
122 | new LookSpike().setVisible(true);
123 | }
124 |
125 | }
126 |
--------------------------------------------------------------------------------
/src/com/jamesshore/finances/ui/_DollarsTextFieldTest.java:
--------------------------------------------------------------------------------
1 | package com.jamesshore.finances.ui;
2 |
3 | import static org.junit.Assert.*;
4 | import java.awt.*;
5 | import javax.swing.*;
6 | import org.junit.*;
7 | import com.jamesshore.finances.values.*;
8 |
9 | public class _DollarsTextFieldTest {
10 |
11 | private DollarsTextField field;
12 | private JPanel iconPanel;
13 | private JTextField textComponent;
14 | private JLabel iconComponent;
15 |
16 | @Before
17 | public void setup() {
18 | field = new DollarsTextField(new ValidDollars(42));
19 |
20 | // Note: for overlay layout to work properly, icon must be first. If you change
21 | // the way components are added to the container, be sure to do a visual check.
22 | Component[] components = field.getComponents();
23 | iconPanel = (JPanel)components[0];
24 | iconComponent = (JLabel)iconPanel.getComponents()[0];
25 | textComponent = (JTextField)components[1];
26 | }
27 |
28 | @Test
29 | public void layout() {
30 | Component[] components = field.getComponents();
31 |
32 | assertEquals("layout", OverlayLayout.class, field.getLayout().getClass());
33 | assertEquals("# of components", 2, components.length);
34 |
35 | FlowLayout iconLayout = (FlowLayout)iconPanel.getLayout();
36 | assertEquals("icon should be contained within a panel", JPanel.class, iconPanel.getClass());
37 | assertFalse("icon panel should be transparent", iconPanel.isOpaque());
38 | assertEquals("icon panel layout", FlowLayout.class, iconLayout.getClass());
39 | assertEquals("icon panel alignment", FlowLayout.RIGHT, iconLayout.getAlignment());
40 |
41 | assertEquals("layout should include icon", JLabel.class, iconComponent.getClass());
42 | assertEquals("layout should include text field", JTextField.class, textComponent.getClass());
43 |
44 | assertFalse("icon should be invisible by default", iconComponent.isVisible());
45 | }
46 |
47 | @Test
48 | public void canSetAndClearIcon() {
49 | ImageIcon icon = new ImageIcon();
50 | field.setIcon(icon);
51 |
52 | assertEquals("icon image", icon, iconComponent.getIcon());
53 | assertTrue("icon label should be visible", iconComponent.isVisible());
54 |
55 | field.setIcon(null);
56 | assertFalse("icon label should not be visible", iconComponent.isVisible());
57 | }
58 |
59 | @Test
60 | public void canGetAndSetArbitraryText() {
61 | field.setText("foo");
62 | assertEquals("foo", field.getText());
63 | }
64 |
65 | @Test
66 | public void settingForegroundColorChangesTextColor() {
67 | field.setForeground(Color.CYAN);
68 | assertEquals("can retrieve color", Color.CYAN, field.getForeground());
69 | assertEquals("actual text color changed", Color.CYAN, textComponent.getForeground());
70 | }
71 |
72 | @Test
73 | public void getForegroundColorIsBasedOnTextColorNotPanelColor() {
74 | textComponent.setForeground(Color.PINK);
75 | assertEquals("color is based on text color", Color.PINK, field.getForeground());
76 | }
77 |
78 | @Test
79 | public void textReflectsDollarAmountUponConstruction() {
80 | assertEquals("$42", field.getText());
81 | }
82 |
83 | @Test
84 | public void canRetrieveAmount() {
85 | assertEquals(new ValidDollars(42), field.getDollars());
86 | }
87 |
88 | @Test
89 | public void changingTextChangesDollarAmount() {
90 | field.setText("1024");
91 | assertEquals(new ValidDollars(1024), field.getDollars());
92 | }
93 |
94 | @Test
95 | public void canCallFunctionWhenTextChanges() {
96 | final boolean[] changed = { false };
97 | DollarsTextField.ChangeListener listener = new DollarsTextField.ChangeListener() {
98 | public void textChanged() {
99 | changed[0] = true;
100 | }
101 | };
102 |
103 | field.addTextChangeListener(listener);
104 | assertFalse("textChanged() should not have been called yet", changed[0]);
105 | field.setText("1000");
106 | assertTrue("textChanged() should have been called", changed[0]);
107 | }
108 |
109 | @Test
110 | public void fieldIsRenderedByDomainClassWhenTextChanges() throws Exception {
111 | field.setText("10");
112 | assertEquals("starts black", Color.BLACK, textComponent.getForeground());
113 | assertFalse("starts with no icon", iconComponent.isVisible());
114 | assertNull("starts with no tooltip", iconComponent.getToolTipText());
115 |
116 | field.setText(" -10 ");
117 | assertEquals("should not change text", " -10 ", textComponent.getText());
118 | assertFalse("should change color", Color.BLACK.equals(textComponent.getForeground()));
119 |
120 | field.setText("xxx");
121 | assertTrue("should set icon", iconComponent.isVisible());
122 | assertNotNull("should set tooltip text", iconComponent.getToolTipText());
123 | }
124 | }
125 |
--------------------------------------------------------------------------------