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 }