001 package market.swing; 002 003 import java.awt.Dimension; 004 import java.awt.event.ItemEvent; 005 import java.awt.event.ItemListener; 006 import java.io.Serializable; 007 008 import javax.swing.ComboBoxModel; 009 import javax.swing.DefaultComboBoxModel; 010 import javax.swing.JComboBox; 011 012 import market.SMarket; 013 import market.event.MarketEventAdapter; 014 import market.statistics.Statistics; 015 016 /** 017 * A set of {@link JComboBox JComboBoxes} for specifying a range of time for which statistics should 018 * be displayed. 019 */ 020 public class JCTimeRangeBoxes implements Serializable { 021 022 /** 023 * The JComboBox for the start date's month. 024 */ 025 private JComboBox jcbFromMonth = new JComboBox(); 026 027 /** 028 * The JComboBox for the start date's year. 029 */ 030 private JComboBox jcbFromYear = new JComboBox(); 031 032 /** 033 * The JComboBox for the finish date's month. 034 */ 035 private JComboBox jcbToMonth = new JComboBox(); 036 037 /** 038 * The JComboBox for the finish date's year. 039 */ 040 private JComboBox jcbToYear = new JComboBox(); 041 private Object[] oFromMonth, oFromYear, oToMonth, oToYear; 042 private Dimension monthBoxDimension = new Dimension(100,25); 043 private Dimension yearBoxDimension = new Dimension(65,25); 044 045 public JCTimeRangeBoxes() { 046 //both preferredSize and minimumSize have to be set: 047 //preferredSize for the size the JComboBox is actually displayed with its borders 048 //minimumSize for the size the BoxLayout manager reserves for displaying the JComboBox 049 jcbFromYear.setPreferredSize(yearBoxDimension); 050 jcbFromYear.setMinimumSize(yearBoxDimension); 051 jcbToYear.setPreferredSize(yearBoxDimension); 052 jcbToYear.setMinimumSize(yearBoxDimension); 053 jcbFromMonth.setPreferredSize(monthBoxDimension); 054 jcbFromMonth.setMinimumSize(monthBoxDimension); 055 jcbToMonth.setPreferredSize(monthBoxDimension); 056 jcbToMonth.setMinimumSize(monthBoxDimension); 057 init(); 058 SMarket.addEventListener(new MarketEventAdapter() { 059 public void timeAdvanced() { 060 init(); 061 } 062 }); 063 jcbFromYear.addItemListener(new FromItemListener()); 064 jcbToYear.addItemListener(new ToItemListener()); 065 } 066 067 /** 068 * Initializes the ComboBoxes' models according to the range of time from which 069 * statistics can be displayed. 070 */ 071 public void init() { 072 jcbFromYear.setModel(new DefaultComboBoxModel(Statistics.createArticleStatisticsYears())); 073 jcbToYear.setModel(new DefaultComboBoxModel(Statistics.createArticleStatisticsYears())); 074 jcbFromMonth.setModel(new DefaultComboBoxModel(Statistics.createArticleStatisticsMonths( 075 jcbFromYear.getSelectedItem()))); 076 jcbToMonth.setModel(new DefaultComboBoxModel(Statistics.createArticleStatisticsMonths( 077 jcbFromYear.getSelectedItem()))); 078 } 079 080 /** 081 * @return the index of the month displayed by {@link #jcbFromMonth}. 082 */ 083 public int getFromMonth() { 084 return getMonth((String)jcbFromMonth.getSelectedItem()); 085 } 086 087 /** 088 * @return the index of the month displayed by {@link #jcbToMonth}. 089 */ 090 public int getToMonth() { 091 return getMonth((String)jcbToMonth.getSelectedItem()); 092 } 093 094 /** 095 * @return the index of the month displayed by {@link #jcbFromYear}. 096 */ 097 public int getFromYear() { 098 return new Integer((String)jcbFromYear.getSelectedItem()).intValue(); 099 } 100 101 /** 102 * @return the index of the month displayed by {@link #jcbToYear}. 103 */ 104 public int getToYear() { 105 return new Integer((String)jcbToYear.getSelectedItem()).intValue(); 106 } 107 108 /** 109 * @param s the name of a month (e.g. Januar or März). 110 * @return the index of the searched month according to the 111 * {@linkplain market.statistics.Statistics#MONTHS months array}. 112 */ 113 public int getMonth(String s) { 114 Object[] months = Statistics.MONTHS; 115 for (int i = 0; i <= 11; i++) { 116 if (months[i].equals(s)) return i; 117 } 118 return -1; 119 } 120 121 /** 122 * Checks if the start of the desired range of time is not after the end of the time range. 123 * @return <code>true</code> if a valid time range was selected, otherwise <code>false</code>. 124 */ 125 public boolean isValidTimeRange() { 126 return (getFromYear() < getToYear()) || 127 (getFromYear() == getToYear() && getFromMonth() <= getToMonth()); 128 } 129 130 /** 131 * Checks if two {@link ComboBoxModel ComboBoxModels} are equal.<br> 132 * This method is used to check if the available months of a box changed. This can happen when 133 * the year's box is changed. 134 * 135 * @see FromItemListener 136 * @see ToItemListener 137 * @param cbm1 first ComboBoxModel to be compared. 138 * @param cbm2 second ComboBoxModel to be compared. 139 * @return <code>true</code> if both models are equal, otherwise <code>false</code>. 140 */ 141 private boolean areModelsEqual(ComboBoxModel cbm1, ComboBoxModel cbm2) { 142 String firstOld = (String)cbm1.getElementAt(0); 143 String lastOld = (String)cbm1.getElementAt(cbm1.getSize() - 1); 144 String firstNew = (String)cbm2.getElementAt(0); 145 String lastNew = (String)cbm2.getElementAt(cbm2.getSize() - 1); 146 return firstOld.equals(firstNew) && lastOld.equals(lastNew); 147 } 148 149 /** 150 * Checks if a month is contained in a JComboBox.<br> 151 * @see FromItemListener 152 * @see ToItemListener 153 * @param jcb the JComboBox to be examined. 154 * @param month the searched month. 155 * @return <code>true</code> if the month is part of the JComboBox, otherwise <code>false</code>. 156 */ 157 private boolean containsMonth(JComboBox jcb, int month) { 158 //searched month later than last month in box? 159 if (month > jcb.getItemCount() - 1) return false; 160 //searched month earlier than first month in box? 161 int firstMonthOfBox = getMonth((String)jcb.getItemAt(0)); 162 return !(firstMonthOfBox > month); 163 } 164 165 /** 166 * @return {@link #jcbFromMonth}. 167 */ 168 public JComboBox getFromMonthBox() { 169 return jcbFromMonth; 170 } 171 172 /** 173 * @return {@link #jcbFromYear}. 174 */ 175 public JComboBox getFromYearBox() { 176 return jcbFromYear; 177 } 178 179 /** 180 * @return {@link #jcbToMonth}. 181 */ 182 public JComboBox getToMonthBox() { 183 return jcbToMonth; 184 } 185 186 /** 187 * @return {@link #jcbToYear}. 188 */ 189 public JComboBox getToYearBox() { 190 return jcbToYear; 191 } 192 193 /** 194 * Listens to state changes on the {@link #jcbFromYear}.<br><br> 195 * Whenever a new year is selected, the {@linkplain #jcbFromMonth month's values} will be 196 * {@linkplain Statistics#createArticleStatisticsMonths recomputed}.<br> 197 * If the JComboBoxes' models {@linkplain #areModelsEqual(ComboBoxModel, ComboBoxModel) are not equal}, 198 * i.e. months were added or removed, the new model is set. But that causes the JComboBox to be set 199 * to its first value, even if that's not necessary.<br> 200 * That's why there is another check, namely if the originally displayed month 201 * {@linkplain #containsMonth(JComboBox, int) is contained} in the new JComboBox. If so, the 202 * JComboBox will be set to this month. 203 */ 204 private class FromItemListener implements ItemListener, Serializable { 205 public void itemStateChanged(ItemEvent e) { 206 if (e.getStateChange() == e.SELECTED) { 207 ComboBoxModel cbm = new DefaultComboBoxModel( 208 Statistics.createArticleStatisticsMonths( 209 jcbFromYear.getSelectedItem())); //update list of available months 210 if (!areModelsEqual(jcbFromMonth.getModel(), cbm)) { //if number of available months changed 211 int oldMonthSet = getFromMonth(); //save currently displayed month 212 String oldMonthString = (String)jcbFromMonth.getSelectedItem(); 213 jcbFromMonth.setModel(cbm); //set new model 214 if (containsMonth(jcbFromMonth, oldMonthSet)) { //if saved month contained in new model 215 jcbFromMonth.setSelectedItem(oldMonthString); //set box to that month 216 } 217 } 218 } 219 } 220 } 221 222 /** 223 * Listens to state changes on the {@link #jcbToYear}.<br><br> 224 * Whenever a new year is selected, the {@linkplain #jcbToMonth month's values} will be 225 * {@linkplain Statistics#createArticleStatisticsMonths recomputed}.<br> 226 * If the JComboBoxes' models {@linkplain #areModelsEqual(ComboBoxModel, ComboBoxModel) are not equal}, 227 * i.e. months were added or removed, the new model is set. But that causes the JComboBox to be set 228 * to its first value, even if that's not necessary.<br> 229 * That's why there is another check, namely if the originally displayed month 230 * {@linkplain #containsMonth(JComboBox, int) is contained} in the new JComboBox. If so, the 231 * JComboBox will be set to this month. 232 */ 233 private class ToItemListener implements ItemListener, Serializable { 234 public void itemStateChanged(ItemEvent e) { 235 if (e.getStateChange() == e.SELECTED) { 236 ComboBoxModel cbm = new DefaultComboBoxModel( 237 Statistics.createArticleStatisticsMonths( 238 jcbToYear.getSelectedItem())); //update list of available months 239 if (!areModelsEqual(jcbToMonth.getModel(), cbm)) { //if number of available months changed 240 int oldMonthSet = getToMonth(); //save currently displayed month 241 String oldMonthString = (String)jcbToMonth.getSelectedItem(); 242 jcbToMonth.setModel(cbm); //set new model 243 if (containsMonth(jcbToMonth, oldMonthSet)) { //if saved month contained in new model 244 jcbToMonth.setSelectedItem(oldMonthString); //set box to that month 245 } 246 } 247 } 248 } 249 } 250 251 }