package org.jdesktop.swingx.plaf.basic; import java.awt.Color; import java.awt.Component; import java.awt.Font; import java.text.DateFormat; import java.text.DateFormatSymbols; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.HashMap; import java.util.Locale; import java.util.Map; import javax.swing.JComponent; import javax.swing.JLabel; import org.jdesktop.swingx.JXMonthView; import org.jdesktop.swingx.decorator.AbstractHighlighter; import org.jdesktop.swingx.decorator.ComponentAdapter; import org.jdesktop.swingx.decorator.CompoundHighlighter; import org.jdesktop.swingx.decorator.HighlightPredicate; import org.jdesktop.swingx.decorator.Highlighter; import org.jdesktop.swingx.decorator.PainterHighlighter; import org.jdesktop.swingx.plaf.UIManagerExt; import org.jdesktop.swingx.renderer.CellContext; import org.jdesktop.swingx.renderer.ComponentProvider; import org.jdesktop.swingx.renderer.FormatStringValue; import org.jdesktop.swingx.renderer.LabelProvider; import org.jdesktop.swingx.renderer.StringValue; import org.jdesktop.swingx.renderer.StringValues; /** * The RenderingHandler responsible for text rendering. It provides * and configures a rendering component for the given cell of * a JXMonthView. <p> * * Note: exposing the createXXStringValue methods is an emergency workaround for * Issue #1062-swingx (core doesn't use arabic digits where appropriate) to allow * subclasses to do better than core. So beware of future changes! * */ class BasicCalendarRenderingHandler implements CalendarRenderingHandler { /** The CellContext for content and default visual config. */ private CalendarCellContext cellContext; /** The providers to use per DayState. */ private Map<CalendarState, ComponentProvider<?>> providers; //-------- Highlight properties /** The Painter used for highlighting unselectable dates. */ private TextCrossingPainter<?> textCross; /** The foreground color for unselectable date highlight. */ private Color unselectableDayForeground; /** * Instantiates a RenderingHandler and installs default state. */ public BasicCalendarRenderingHandler() { install(); } private void install() { unselectableDayForeground = UIManagerExt.getColor("JXMonthView.unselectableDayForeground"); textCross = new TextCrossingPainter<JLabel>(); cellContext = new CalendarCellContext(); installProviders(); } /** * Creates and stores ComponentProviders for all DayStates. */ private void installProviders() { providers = new HashMap<CalendarState, ComponentProvider<?>>(); StringValue sv = createDayStringValue(null); ComponentProvider<?> provider = new LabelProvider(sv, JLabel.RIGHT); providers.put(CalendarState.IN_MONTH, provider); providers.put(CalendarState.TODAY, provider); providers.put(CalendarState.TRAILING, provider); providers.put(CalendarState.LEADING, provider); StringValue wsv = createWeekOfYearStringValue(null); ComponentProvider<?> weekOfYearProvider = new LabelProvider(wsv, JLabel.RIGHT); providers.put(CalendarState.WEEK_OF_YEAR, weekOfYearProvider); ComponentProvider<?> dayOfWeekProvider = new LabelProvider(JLabel.CENTER) { @Override protected String getValueAsString(CellContext context) { Object value = context.getValue(); // PENDING JW: this is breaking provider's contract in its // role as StringValue! Don't in the general case. if (value instanceof Calendar) { int day = ((Calendar) value).get(Calendar.DAY_OF_WEEK); return ((JXMonthView) context.getComponent()).getDayOfTheWeek(day); } return super.getValueAsString(context); } }; providers.put(CalendarState.DAY_OF_WEEK, dayOfWeekProvider); StringValue tsv = createMonthHeaderStringValue(null); ComponentProvider<?> titleProvider = new LabelProvider(tsv, JLabel.CENTER); providers.put(CalendarState.TITLE, titleProvider); } /** * Creates and returns a StringValue used for rendering the title of a month box. * The input they are assumed to handle is a Calendar configured to a day of * the month to render. * * @param locale the Locale to use, might be null to indicate usage of the default * Locale * @return a StringValue appropriate for rendering month title. */ protected StringValue createMonthHeaderStringValue(Locale locale) { if (locale == null) { locale = Locale.getDefault(); } final String[] monthNames = DateFormatSymbols.getInstance(locale).getMonths(); StringValue tsv = new StringValue() { @Override public String getString(Object value) { if (value instanceof Calendar) { String month = monthNames[((Calendar) value) .get(Calendar.MONTH)]; return month + " " + ((Calendar) value).get(Calendar.YEAR); } return StringValues.TO_STRING.getString(value); } }; return tsv; } /** * Creates and returns a StringValue used for rendering the week of year. * The input they are assumed to handle is a Calendar configured to a day of * the week to render. * * @param locale the Locale to use, might be null to indicate usage of the default * Locale * @return a StringValue appropriate for rendering week of year. */ protected StringValue createWeekOfYearStringValue(Locale locale) { StringValue wsv = new StringValue() { @Override public String getString(Object value) { if (value instanceof Calendar) { value = ((Calendar) value).get(Calendar.WEEK_OF_YEAR); } return StringValues.TO_STRING.getString(value); } }; return wsv; } /** * Creates and returns a StringValue used for rendering days in a month. * The input they are assumed to handle is a Calendar configured to the day. * * @param locale the Locale to use, might be null to indicate usage of the default * Locale * @return a StringValue appropriate for rendering days in a month */ protected StringValue createDayStringValue(Locale locale) { if (locale == null) { locale = Locale.getDefault(); } FormatStringValue sv = new FormatStringValue(new SimpleDateFormat("d", locale)) { @Override public String getString(Object value) { if (value instanceof Calendar) { ((DateFormat) getFormat()).setTimeZone(((Calendar) value).getTimeZone()); value = ((Calendar) value).getTime(); } return super.getString(value); } }; return sv; } /** * Updates internal state to the given Locale. * * @param locale the new Locale. */ @Override public void setLocale(Locale locale) { StringValue dayValue = createDayStringValue(locale); providers.get(CalendarState.IN_MONTH).setStringValue(dayValue); providers.get(CalendarState.TODAY).setStringValue(dayValue); providers.get(CalendarState.TRAILING).setStringValue(dayValue); providers.get(CalendarState.LEADING).setStringValue(dayValue); providers.get(CalendarState.WEEK_OF_YEAR).setStringValue(createWeekOfYearStringValue(locale)); providers.get(CalendarState.TITLE).setStringValue(createMonthHeaderStringValue(locale)); } /** * Configures and returns a component for rendering of the given monthView cell. * * @param monthView the JXMonthView to render onto * @param calendar the cell value * @param dayState the DayState of the cell * @return a component configured for rendering the given cell */ @Override public JComponent prepareRenderingComponent(JXMonthView monthView, Calendar calendar, CalendarState dayState) { cellContext.installContext(monthView, calendar, isSelected(monthView, calendar, dayState), isFocused(monthView, calendar, dayState), dayState); JComponent comp = providers.get(dayState).getRendererComponent(cellContext); return highlight(comp, monthView, calendar, dayState); } /** * * NOTE: it's the responsibility of the CalendarCellContext to detangle * all "default" (that is: which could be queried from the comp and/or UIManager) * foreground/background colors based on the given state! Moved out off here. * <p> * PENDING JW: replace hard-coded logic by giving over to highlighters. * * @param monthView the JXMonthView to render onto * @param calendar the cell value * @param dayState the DayState of the cell * @param dayState */ private JComponent highlight(JComponent comp, JXMonthView monthView, Calendar calendar, CalendarState dayState) { CalendarAdapter adapter = getCalendarAdapter(monthView, calendar, dayState); return (JComponent) getHighlighter().highlight(comp, adapter); } /** * @return */ private Highlighter getHighlighter() { if (highlighter == null) { highlighter = new CompoundHighlighter(); installHighlighters(); } return highlighter; } /** * */ private void installHighlighters() { HighlightPredicate boldPredicate = new HighlightPredicate() { @Override public boolean isHighlighted(Component renderer, ComponentAdapter adapter) { if (!(adapter instanceof CalendarAdapter)) return false; CalendarAdapter ca = (CalendarAdapter) adapter; return CalendarState.DAY_OF_WEEK == ca.getCalendarState() || CalendarState.TITLE == ca.getCalendarState(); } }; Highlighter font = new AbstractHighlighter(boldPredicate) { @Override protected Component doHighlight(Component component, ComponentAdapter adapter) { component.setFont(getDerivedFont(component.getFont())); return component; } }; highlighter.addHighlighter(font); HighlightPredicate unselectable = new HighlightPredicate() { @Override public boolean isHighlighted(Component renderer, ComponentAdapter adapter) { if (!(adapter instanceof CalendarAdapter)) return false; return ((CalendarAdapter) adapter).isUnselectable(); } }; textCross.setForeground(unselectableDayForeground); Highlighter painterHL = new PainterHighlighter(unselectable, textCross); highlighter.addHighlighter(painterHL); } /** * @param monthView * @param calendar * @param dayState * @return */ private CalendarAdapter getCalendarAdapter(JXMonthView monthView, Calendar calendar, CalendarState dayState) { if (calendarAdapter == null) { calendarAdapter = new CalendarAdapter(monthView); } return calendarAdapter.install(calendar, dayState); } private CalendarAdapter calendarAdapter; private CompoundHighlighter highlighter; /** * @param font * @return */ private Font getDerivedFont(Font font) { return font.deriveFont(Font.BOLD); } /** * @param monthView * @param calendar * @param dayState * @return */ private boolean isFocused(JXMonthView monthView, Calendar calendar, CalendarState dayState) { return false; } /** * @param monthView the JXMonthView to render onto * @param calendar the cell value * @param dayState the DayState of the cell * @return */ private boolean isSelected(JXMonthView monthView, Calendar calendar, CalendarState dayState) { if (!isSelectable(dayState)) return false; return monthView.isSelected(calendar.getTime()); } /** * @param dayState * @return */ private boolean isSelectable(CalendarState dayState) { return (CalendarState.IN_MONTH == dayState) || (CalendarState.TODAY == dayState); } }