├── .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 | --------------------------------------------------------------------------------