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 }