001    /*
002     * $Id: CalendarHeaderHandler.java 3331 2009-04-23 11:46:54Z kleopatra $
003     *
004     * Copyright 2007 Sun Microsystems, Inc., 4150 Network Circle,
005     * Santa Clara, California 95054, U.S.A. All rights reserved.
006     *
007     * This library is free software; you can redistribute it and/or
008     * modify it under the terms of the GNU Lesser General Public
009     * License as published by the Free Software Foundation; either
010     * version 2.1 of the License, or (at your option) any later version.
011     * 
012     * This library is distributed in the hope that it will be useful,
013     * but WITHOUT ANY WARRANTY; without even the implied warranty of
014     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015     * Lesser General Public License for more details.
016     * 
017     * You should have received a copy of the GNU Lesser General Public
018     * License along with this library; if not, write to the Free Software
019     * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
020     *
021     */
022    package org.jdesktop.swingx.plaf.basic;
023    
024    import java.awt.Color;
025    import java.awt.Font;
026    import java.awt.event.ActionEvent;
027    import java.beans.PropertyChangeEvent;
028    import java.beans.PropertyChangeListener;
029    import java.util.logging.Logger;
030    
031    import javax.swing.Action;
032    import javax.swing.Icon;
033    import javax.swing.JComponent;
034    import javax.swing.UIManager;
035    import javax.swing.plaf.UIResource;
036    
037    import org.jdesktop.swingx.JXMonthView;
038    import org.jdesktop.swingx.action.AbstractActionExt;
039    
040    /**
041     * Provides and wires a component appropriate as a calendar navigation header.
042     * The design idea is to support a pluggable header for a zoomable (PENDING JW:
043     * naming!) JXMonthView. Then custom implementations can be tailored to exactly
044     * fit their needs.
045     * <p>
046     * 
047     * To install a custom implementation, register the class name of the custom
048     * header handler with the key <code>CalendarHeaderHandler.uiControllerID</code>
049     * , example:
050     * 
051     * <pre>
052     * <code>
053     *  UIManager.put(CalendarHeaderHandler.uiControllerID, &quot;com.foo.bar.MagicHeaderHandler&quot;)
054     * </code>
055     * </pre>
056     * 
057     * Basic navigation action should (will) be defined by the ui delegate itself (PENDING
058     * JW: still incomplete in BasicMonthViewUI). This handler can modify/enhance
059     * them as appropriate for its context.
060     * <p>
061     * 
062     * PENDING JW: those icons ... who's responsible? Shouldn't we use any of the
063     * default arrows as defined in the laf anyway (are there any?)
064     * <p>
065     * 
066     * <b>Note</b>: this is work-in-progress, be prepared to change if subclassing
067     * for custom requirements!
068     * 
069     * @author Jeanette Winzenburg
070     */
071    public abstract class CalendarHeaderHandler {
072    
073        @SuppressWarnings("unused")
074        private static final Logger LOG = Logger
075                .getLogger(CalendarHeaderHandler.class.getName());
076    
077        public static final String uiControllerID = "CalendarHeaderHandler";
078    
079        protected JXMonthView monthView;
080    
081        private JComponent calendarHeader;
082    
083        protected Icon monthDownImage;
084    
085        protected Icon monthUpImage;
086    
087        private PropertyChangeListener monthViewPropertyChangeListener;
088    
089        /**
090         * Installs this handler to the given month view.
091         * 
092         * @param monthView the target month view to install to.
093         */
094        public void install(JXMonthView monthView) {
095            this.monthView = monthView;
096            // PENDING JW: remove here if rendererHandler takes over control
097            // completely
098            // as is, some properties are duplicated
099            monthDownImage = UIManager.getIcon("JXMonthView.monthDownFileName");
100            monthUpImage = UIManager.getIcon("JXMonthView.monthUpFileName");
101            installNavigationActions();
102            installListeners();
103            componentOrientationChanged();
104            monthStringBackgroundChanged();
105            fontChanged();
106        }
107    
108        /**
109         * Uninstalls this handler from the given target month view.
110         * 
111         * @param monthView the target month view to install from.
112         */
113        public void uninstall(JXMonthView monthView) {
114            this.monthView.remove(getHeaderComponent());
115            uninstallListeners();
116            this.monthView = null;
117        }
118    
119        /**
120         * Returns a component to be used as header in a zoomable month view,
121         * guaranteed to be not null.
122         * 
123         * @return a component to be used as header in a zoomable JXMonthView
124         */
125        public JComponent getHeaderComponent() {
126            if (calendarHeader == null) {
127                calendarHeader = createCalendarHeader();
128            }
129            return calendarHeader;
130        }
131    
132        /**
133         * Creates and registered listeners on the monthView as appropriate. This
134         * implementation registers a PropertyChangeListener which synchronizes
135         * internal state on changes of componentOrientation, font and
136         * monthStringBackground.
137         */
138        protected void installListeners() {
139            monthView
140                    .addPropertyChangeListener(getMonthViewPropertyChangeListener());
141        }
142    
143        /**
144         * Unregisters listeners which had been installed to the monthView.
145         */
146        protected void uninstallListeners() {
147            monthView.removePropertyChangeListener(monthViewPropertyChangeListener);
148        }
149    
150        /**
151         * Returns the propertyChangelistener for the monthView. Lazily created.
152         * 
153         * @return the propertyChangeListener for the monthView.
154         */
155        private PropertyChangeListener getMonthViewPropertyChangeListener() {
156            if (monthViewPropertyChangeListener == null) {
157                monthViewPropertyChangeListener = new PropertyChangeListener() {
158    
159                    public void propertyChange(PropertyChangeEvent evt) {
160                        if ("componentOrientation".equals(evt.getPropertyName())) {
161                            componentOrientationChanged();
162                        } else if ("font".equals(evt.getPropertyName())) {
163                            fontChanged();
164                        } else if ("monthStringBackground".equals(evt
165                                .getPropertyName())) {
166                            monthStringBackgroundChanged();
167                        }
168    
169                    }
170                };
171            }
172            return monthViewPropertyChangeListener;
173        }
174    
175        /**
176         * Synchronizes internal state which depends on the month view's
177         * monthStringBackground.
178         */
179        protected void monthStringBackgroundChanged() {
180            getHeaderComponent().setBackground(
181                    getAsNotUIResource(monthView.getMonthStringBackground()));
182    
183        }
184    
185        /**
186         * Synchronizes internal state which depends on the month view's font.
187         */
188        protected void fontChanged() {
189            getHeaderComponent().setFont(getAsNotUIResource(createDerivedFont()));
190            monthView.revalidate();
191        }
192    
193        /**
194         * Synchronizes internal state which depends on the month view's
195         * componentOrientation.
196         * 
197         * This implementation updates the month navigation icons and the header
198         * component's orientation.
199         */
200        protected void componentOrientationChanged() {
201            getHeaderComponent().applyComponentOrientation(
202                    monthView.getComponentOrientation());
203            if (monthView.getComponentOrientation().isLeftToRight()) {
204                updateMonthNavigationIcons(monthDownImage, monthUpImage);
205            } else {
206                updateMonthNavigationIcons(monthUpImage, monthDownImage);
207            }
208        }
209    
210        /**
211         * @param previous the icon to use in the previousMonth action
212         * @param next the icon to use on the nextMonth action
213         */
214        private void updateMonthNavigationIcons(Icon previous, Icon next) {
215            updateActionIcon("previousMonth", previous);
216            updateActionIcon("nextMonth", next);
217        }
218    
219        /**
220         * @param previousKey
221         * @param previous
222         */
223        private void updateActionIcon(String previousKey, Icon previous) {
224            Action action = monthView.getActionMap().get(previousKey);
225            if (action != null) {
226                action.putValue(Action.SMALL_ICON, previous);
227            }
228        }
229    
230        /**
231         * Creates and returns the component used as header in a zoomable monthView.
232         * 
233         * @return the component used as header in a zoomable monthView, guaranteed
234         *         to be not null.
235         */
236        protected abstract JComponent createCalendarHeader();
237    
238        /**
239         * Installs and configures navigational actions.
240         * <p>
241         * 
242         * This implementation creates and installs wrappers around the
243         * scrollToPrevious/-NextMonth actions installed by the ui and configures
244         * them with the appropriate next/previous icons.
245         */
246        protected void installNavigationActions() {
247            installWrapper("scrollToPreviousMonth", "previousMonth", monthView
248                    .getComponentOrientation().isLeftToRight() ? monthDownImage
249                    : monthUpImage);
250            installWrapper("scrollToNextMonth", "nextMonth", monthView
251                    .getComponentOrientation().isLeftToRight() ? monthUpImage
252                    : monthDownImage);
253        }
254    
255        /**
256         * Creates an life action wrapper around the action registered with
257         * actionKey, sets its SMALL_ICON property to the given icon and installs
258         * itself with the newActionKey.
259         * 
260         * @param actionKey the key of the action to wrap around
261         * @param newActionKey the key of the wrapper action
262         * @param icon the icon to use in the wrapper action
263         */
264        private void installWrapper(final String actionKey, String newActionKey,
265                Icon icon) {
266            AbstractActionExt wrapper = new AbstractActionExt(null, icon) {
267    
268                public void actionPerformed(ActionEvent e) {
269                    Action action = monthView.getActionMap().get(actionKey);
270                    if (action != null) {
271                        action.actionPerformed(e);
272                    }
273                }
274    
275            };
276            monthView.getActionMap().put(newActionKey, wrapper);
277        }
278    
279        /**
280         * Returns a Font based on the param which is not of type UIResource.
281         * 
282         * @param font the base font
283         * @return a font not of type UIResource, may be null.
284         */
285        private Font getAsNotUIResource(Font font) {
286            if (!(font instanceof UIResource))
287                return font;
288            // PENDING JW: correct way to create another font instance?
289            return font.deriveFont(font.getAttributes());
290        }
291    
292        /**
293         * Returns a Color based on the param which is not of type UIResource.
294         * 
295         * @param color the base color
296         * @return a color not of type UIResource, may be null.
297         */
298        private Color getAsNotUIResource(Color color) {
299            if (!(color instanceof UIResource))
300                return color;
301            // PENDING JW: correct way to create another color instance?
302            float[] rgb = color.getRGBComponents(null);
303            return new Color(rgb[0], rgb[1], rgb[2], rgb[3]);
304        }
305    
306        /**
307         * Create a derived font used to when painting various pieces of the month
308         * view component. This method will be called whenever the font on the
309         * component is set so a new derived font can be created.
310         */
311        protected Font createDerivedFont() {
312            return monthView.getFont().deriveFont(Font.BOLD);
313        }
314    
315    }