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