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, "com.foo.bar.MagicHeaderHandler")
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 }