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 }