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    }