001/* 002 * $Id: CalendarHeaderHandler.java 3927 2011-02-22 16:34:11Z 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 */ 022package org.jdesktop.swingx.plaf.basic; 023 024import java.awt.Color; 025import java.awt.Font; 026import java.awt.event.ActionEvent; 027import java.beans.PropertyChangeEvent; 028import java.beans.PropertyChangeListener; 029import java.util.logging.Logger; 030 031import javax.swing.Action; 032import javax.swing.Icon; 033import javax.swing.JComponent; 034import javax.swing.UIManager; 035import javax.swing.plaf.UIResource; 036 037import org.jdesktop.swingx.JXMonthView; 038import 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 */ 071public 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 @Override 160 public void propertyChange(PropertyChangeEvent evt) { 161 if ("componentOrientation".equals(evt.getPropertyName())) { 162 componentOrientationChanged(); 163 } else if ("font".equals(evt.getPropertyName())) { 164 fontChanged(); 165 } else if ("monthStringBackground".equals(evt 166 .getPropertyName())) { 167 monthStringBackgroundChanged(); 168 } 169 170 } 171 }; 172 } 173 return monthViewPropertyChangeListener; 174 } 175 176 /** 177 * Synchronizes internal state which depends on the month view's 178 * monthStringBackground. 179 */ 180 protected void monthStringBackgroundChanged() { 181 getHeaderComponent().setBackground( 182 getAsNotUIResource(monthView.getMonthStringBackground())); 183 184 } 185 186 /** 187 * Synchronizes internal state which depends on the month view's font. 188 */ 189 protected void fontChanged() { 190 getHeaderComponent().setFont(getAsNotUIResource(createDerivedFont())); 191 monthView.revalidate(); 192 } 193 194 /** 195 * Synchronizes internal state which depends on the month view's 196 * componentOrientation. 197 * 198 * This implementation updates the month navigation icons and the header 199 * component's orientation. 200 */ 201 protected void componentOrientationChanged() { 202 getHeaderComponent().applyComponentOrientation( 203 monthView.getComponentOrientation()); 204 if (monthView.getComponentOrientation().isLeftToRight()) { 205 updateMonthNavigationIcons(monthDownImage, monthUpImage); 206 } else { 207 updateMonthNavigationIcons(monthUpImage, monthDownImage); 208 } 209 } 210 211 /** 212 * @param previous the icon to use in the previousMonth action 213 * @param next the icon to use on the nextMonth action 214 */ 215 private void updateMonthNavigationIcons(Icon previous, Icon next) { 216 updateActionIcon("previousMonth", previous); 217 updateActionIcon("nextMonth", next); 218 } 219 220 /** 221 * @param previousKey 222 * @param previous 223 */ 224 private void updateActionIcon(String previousKey, Icon previous) { 225 Action action = monthView.getActionMap().get(previousKey); 226 if (action != null) { 227 action.putValue(Action.SMALL_ICON, previous); 228 } 229 } 230 231 /** 232 * Creates and returns the component used as header in a zoomable monthView. 233 * 234 * @return the component used as header in a zoomable monthView, guaranteed 235 * to be not null. 236 */ 237 protected abstract JComponent createCalendarHeader(); 238 239 /** 240 * Installs and configures navigational actions. 241 * <p> 242 * 243 * This implementation creates and installs wrappers around the 244 * scrollToPrevious/-NextMonth actions installed by the ui and configures 245 * them with the appropriate next/previous icons. 246 */ 247 protected void installNavigationActions() { 248 installWrapper("scrollToPreviousMonth", "previousMonth", monthView 249 .getComponentOrientation().isLeftToRight() ? monthDownImage 250 : monthUpImage); 251 installWrapper("scrollToNextMonth", "nextMonth", monthView 252 .getComponentOrientation().isLeftToRight() ? monthUpImage 253 : monthDownImage); 254 } 255 256 /** 257 * Creates an life action wrapper around the action registered with 258 * actionKey, sets its SMALL_ICON property to the given icon and installs 259 * itself with the newActionKey. 260 * 261 * @param actionKey the key of the action to wrap around 262 * @param newActionKey the key of the wrapper action 263 * @param icon the icon to use in the wrapper action 264 */ 265 private void installWrapper(final String actionKey, String newActionKey, 266 Icon icon) { 267 AbstractActionExt wrapper = new AbstractActionExt(null, icon) { 268 269 @Override 270 public void actionPerformed(ActionEvent e) { 271 Action action = monthView.getActionMap().get(actionKey); 272 if (action != null) { 273 action.actionPerformed(e); 274 } 275 } 276 277 }; 278 monthView.getActionMap().put(newActionKey, wrapper); 279 } 280 281 /** 282 * Returns a Font based on the param which is not of type UIResource. 283 * 284 * @param font the base font 285 * @return a font not of type UIResource, may be null. 286 */ 287 private Font getAsNotUIResource(Font font) { 288 if (!(font instanceof UIResource)) 289 return font; 290 // PENDING JW: correct way to create another font instance? 291 return font.deriveFont(font.getAttributes()); 292 } 293 294 /** 295 * Returns a Color based on the param which is not of type UIResource. 296 * 297 * @param color the base color 298 * @return a color not of type UIResource, may be null. 299 */ 300 private Color getAsNotUIResource(Color color) { 301 if (!(color instanceof UIResource)) 302 return color; 303 // PENDING JW: correct way to create another color instance? 304 float[] rgb = color.getRGBComponents(null); 305 return new Color(rgb[0], rgb[1], rgb[2], rgb[3]); 306 } 307 308 /** 309 * Create a derived font used to when painting various pieces of the month 310 * view component. This method will be called whenever the font on the 311 * component is set so a new derived font can be created. 312 */ 313 protected Font createDerivedFont() { 314 return monthView.getFont().deriveFont(Font.BOLD); 315 } 316 317}