package org.holoeverywhere.widget; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Locale; import org.holoeverywhere.LayoutInflater; import com.actionbarsherlock.R; import org.holoeverywhere.internal.NumberPickerEditText; import org.holoeverywhere.util.Arrays; import android.annotation.SuppressLint; import android.content.Context; import android.content.res.Configuration; import android.content.res.TypedArray; import android.os.Build.VERSION; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; import android.text.format.DateFormat; import android.text.format.DateUtils; import android.util.AttributeSet; import android.util.Log; import android.util.SparseArray; import android.view.View; import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; // TODO: Auto-generated Javadoc /** * The Class DatePicker. */ @SuppressLint("SimpleDateFormat") public class DatePicker extends FrameLayout { /** * The Class Callback. */ private final class Callback implements NumberPicker.OnValueChangeListener, CalendarView.OnDateChangeListener { /* (non-Javadoc) * @see org.holoeverywhere.widget.CalendarView.OnDateChangeListener#onSelectedDayChange(org.holoeverywhere.widget.CalendarView, int, int, int) */ @Override public void onSelectedDayChange(CalendarView view, int year, int month, int monthDay) { setDate(year, month, monthDay); updateSpinners(); notifyDateChanged(); } /* (non-Javadoc) * @see org.holoeverywhere.widget.NumberPicker.OnValueChangeListener#onValueChange(org.holoeverywhere.widget.NumberPicker, int, int) */ @Override public void onValueChange(NumberPicker picker, int oldVal, int newVal) { updateInputState(); tempDate.setTimeInMillis(currentDate.getTimeInMillis()); if (picker == daySpinner) { int maxDayOfMonth = tempDate .getActualMaximum(Calendar.DAY_OF_MONTH); if (oldVal == maxDayOfMonth && newVal == 1) { tempDate.add(Calendar.DAY_OF_MONTH, 1); } else if (oldVal == 1 && newVal == maxDayOfMonth) { tempDate.add(Calendar.DAY_OF_MONTH, -1); } else { tempDate.add(Calendar.DAY_OF_MONTH, newVal - oldVal); } } else if (picker == monthSpinner) { if (oldVal == 11 && newVal == 0) { tempDate.add(Calendar.MONTH, 1); } else if (oldVal == 0 && newVal == 11) { tempDate.add(Calendar.MONTH, -1); } else { tempDate.add(Calendar.MONTH, newVal - oldVal); } } else if (picker == yearSpinner) { tempDate.set(Calendar.YEAR, newVal); } else { return; } setDate(tempDate.get(Calendar.YEAR), tempDate.get(Calendar.MONTH), tempDate.get(Calendar.DAY_OF_MONTH)); updateSpinners(); updateCalendarView(); notifyDateChanged(); } } /** * The listener interface for receiving onDateChanged events. * The class that is interested in processing a onDateChanged * event implements this interface, and the object created * with that class is registered with a component using the * component's <code>addOnDateChangedListener<code> method. When * the onDateChanged event occurs, that object's appropriate * method is invoked. * * @see OnDateChangedEvent */ public interface OnDateChangedListener { /** * On date changed. * * @param view the view * @param year the year * @param monthOfYear the month of year * @param dayOfMonth the day of month */ void onDateChanged(DatePicker view, int year, int monthOfYear, int dayOfMonth); } /** * The Class SavedState. */ private static class SavedState extends BaseSavedState { /** The Constant CREATOR. */ @SuppressWarnings("all") public static final Creator<SavedState> CREATOR = new Creator<SavedState>() { public SavedState createFromParcel(Parcel in) { return new SavedState(in); } public SavedState[] newArray(int size) { return new SavedState[size]; } }; /** The day. */ private final int year, month, day; /** * Instantiates a new saved state. * * @param in the in */ private SavedState(Parcel in) { super(in); year = in.readInt(); month = in.readInt(); day = in.readInt(); } /** * Instantiates a new saved state. * * @param superState the super state * @param year the year * @param month the month * @param day the day */ private SavedState(Parcelable superState, int year, int month, int day) { super(superState); this.year = year; this.month = month; this.day = day; } /* (non-Javadoc) * @see android.view.AbsSavedState#writeToParcel(android.os.Parcel, int) */ @Override public void writeToParcel(Parcel dest, int flags) { super.writeToParcel(dest, flags); dest.writeInt(year); dest.writeInt(month); dest.writeInt(day); } } /** The Constant DATE_FORMAT. */ private static final String DATE_FORMAT = "MM/dd/yyyy"; /** The Constant LOG_TAG. */ private static final String LOG_TAG = DatePicker.class.getSimpleName(); /** * Gets the calendar for locale. * * @param oldCalendar the old calendar * @param locale the locale * @return the calendar for locale */ private static Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) { if (oldCalendar == null) { return Calendar.getInstance(locale); } else { final long currentTimeMillis = oldCalendar.getTimeInMillis(); Calendar newCalendar = Calendar.getInstance(locale); newCalendar.setTimeInMillis(currentTimeMillis); return newCalendar; } } /** * Sets the content description. * * @param parent the parent * @param childId the child id * @param textId the text id */ private static void setContentDescription(View parent, int childId, int textId) { if (parent == null) { return; } View child = parent.findViewById(childId); if (child != null) { child.setContentDescription(parent.getContext().getText(textId)); } } /** The callback. */ private final Callback callback = new Callback(); /** The date format. */ private final java.text.DateFormat dateFormat = new SimpleDateFormat( DatePicker.DATE_FORMAT); /** The year spinner. */ private final NumberPicker daySpinner, monthSpinner, yearSpinner; /** The input method manager. */ private final InputMethodManager inputMethodManager; /** The locale. */ private Locale locale; /** The m calendar view. */ private final CalendarView mCalendarView; /** The number of months. */ private int numberOfMonths; /** The on date changed listener. */ private OnDateChangedListener onDateChangedListener; /** The short months. */ private String[] shortMonths; /** The spinners. */ private final LinearLayout spinners; /** The current date. */ private Calendar tempDate, minDate, maxDate, currentDate; /** * Instantiates a new date picker. * * @param context the context */ public DatePicker(Context context) { this(context, null); } /** * Instantiates a new date picker. * * @param context the context * @param attrs the attrs */ public DatePicker(Context context, AttributeSet attrs) { this(context, attrs, R.attr.datePickerStyle); } /** * Instantiates a new date picker. * * @param context the context * @param attrs the attrs * @param defStyle the def style */ public DatePicker(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DatePicker, defStyle, R.style.Holo_DatePicker); boolean spinnersShown = a.getBoolean( R.styleable.DatePicker_spinnersShown, true); boolean calendarViewShown = a.getBoolean( R.styleable.DatePicker_calendarViewShown, true); boolean forceShownState = a.getBoolean( R.styleable.DatePicker_forceShownState, false); int startYear = a.getInt(R.styleable.DatePicker_startYear, 1900); int endYear = a.getInt(R.styleable.DatePicker_endYear, 2100); String minDate = a.getString(R.styleable.DatePicker_minDate); String maxDate = a.getString(R.styleable.DatePicker_maxDate); int layoutResourceId = a.getResourceId(R.styleable.DatePicker_layout, R.layout.he_date_picker_holo); a.recycle(); inputMethodManager = (InputMethodManager) context .getSystemService(Context.INPUT_METHOD_SERVICE); setLocale(Locale.getDefault()); LayoutInflater.inflate(context, layoutResourceId, this, true); spinners = (LinearLayout) findViewById(R.id.pickers); mCalendarView = (CalendarView) findViewById(R.id.calendar_view); daySpinner = (NumberPicker) findViewById(R.id.day); monthSpinner = (NumberPicker) findViewById(R.id.month); yearSpinner = (NumberPicker) findViewById(R.id.year); if (((AccessibilityManager) getContext().getSystemService( Context.ACCESSIBILITY_SERVICE)).isEnabled()) { setContentDescriptions(); } mCalendarView.setOnDateChangeListener(callback); daySpinner.setFormatter(NumberPicker.TWO_DIGIT_FORMATTER); daySpinner.setOnLongPressUpdateInterval(100); daySpinner.setOnValueChangedListener(callback); monthSpinner.setMinValue(0); monthSpinner.setMaxValue(numberOfMonths - 1); monthSpinner.setDisplayedValues(shortMonths); monthSpinner.setOnLongPressUpdateInterval(200); monthSpinner.setOnValueChangedListener(callback); yearSpinner.setOnLongPressUpdateInterval(100); yearSpinner.setOnValueChangedListener(callback); if (spinnersShown || calendarViewShown || forceShownState) { setSpinnersShown(spinnersShown); setCalendarViewShown(calendarViewShown); } else { setSpinnersShown(true); setCalendarViewShown(false); } tempDate.clear(); if (TextUtils.isEmpty(minDate) || !parseDate(minDate, tempDate)) { tempDate.set(startYear, 0, 1); } setMinDate(tempDate.getTimeInMillis()); tempDate.clear(); if (TextUtils.isEmpty(maxDate) || !parseDate(maxDate, tempDate)) { tempDate.set(endYear, 11, 31); } setMaxDate(tempDate.getTimeInMillis()); currentDate.setTimeInMillis(System.currentTimeMillis()); init(currentDate.get(Calendar.YEAR), currentDate.get(Calendar.MONTH), currentDate.get(Calendar.DAY_OF_MONTH), null); reorderSpinners(); } /** * Check input state. * * @param spinners the spinners */ private void checkInputState(NumberPicker... spinners) { for (NumberPicker spinner : spinners) { NumberPickerEditText input = spinner.getInputField(); if (inputMethodManager.isActive(input)) { input.clearFocus(); inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0); } } } /* (non-Javadoc) * @see android.view.View#dispatchPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent) */ @SuppressLint("NewApi") @Override public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { onPopulateAccessibilityEvent(event); return true; } /* (non-Javadoc) * @see android.view.ViewGroup#dispatchRestoreInstanceState(android.util.SparseArray) */ @Override protected void dispatchRestoreInstanceState( SparseArray<Parcelable> container) { dispatchThawSelfOnly(container); } /** * Gets the calendar view. * * @return the calendar view */ public CalendarView getCalendarView() { return mCalendarView; } /** * Gets the calendar view shown. * * @return the calendar view shown */ public boolean getCalendarViewShown() { return mCalendarView.isShown(); } /** * Gets the day of month. * * @return the day of month */ public int getDayOfMonth() { return currentDate.get(Calendar.DAY_OF_MONTH); } /** * Gets the max date. * * @return the max date */ public long getMaxDate() { return mCalendarView.getMaxDate(); } /** * Gets the min date. * * @return the min date */ public long getMinDate() { return mCalendarView.getMinDate(); } /** * Gets the month. * * @return the month */ public int getMonth() { return currentDate.get(Calendar.MONTH); } /** * Gets the on date changed listener. * * @return the on date changed listener */ public OnDateChangedListener getOnDateChangedListener() { return onDateChangedListener; } /** * Gets the spinners shown. * * @return the spinners shown */ public boolean getSpinnersShown() { return spinners.isShown(); } /** * Gets the year. * * @return the year */ public int getYear() { return currentDate.get(Calendar.YEAR); } /** * Inits the. * * @param year the year * @param monthOfYear the month of year * @param dayOfMonth the day of month * @param onDateChangedListener the on date changed listener */ public void init(int year, int monthOfYear, int dayOfMonth, OnDateChangedListener onDateChangedListener) { setOnDateChangedListener(onDateChangedListener); setDate(year, monthOfYear, dayOfMonth); updateSpinners(); updateCalendarView(); } /** * Checks if is new date. * * @param year the year * @param month the month * @param dayOfMonth the day of month * @return true, if is new date */ private boolean isNewDate(int year, int month, int dayOfMonth) { return currentDate.get(Calendar.YEAR) != year || currentDate.get(Calendar.MONTH) != dayOfMonth || currentDate.get(Calendar.DAY_OF_MONTH) != month; } /** * Notify date changed. */ private void notifyDateChanged() { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); if (onDateChangedListener != null) { onDateChangedListener.onDateChanged(this, getYear(), getMonth(), getDayOfMonth()); } } /* (non-Javadoc) * @see android.view.View#onConfigurationChanged(android.content.res.Configuration) */ @Override @SuppressLint("NewApi") protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); setLocale(newConfig.locale); } /* (non-Javadoc) * @see android.view.View#onPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent) */ @SuppressLint("NewApi") @Override public void onPopulateAccessibilityEvent(AccessibilityEvent event) { if (VERSION.SDK_INT >= 14) { super.onPopulateAccessibilityEvent(event); } final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR; String selectedDateUtterance = DateUtils.formatDateTime(getContext(), currentDate.getTimeInMillis(), flags); event.getText().add(selectedDateUtterance); } /* (non-Javadoc) * @see android.view.View#onRestoreInstanceState(android.os.Parcelable) */ @Override protected void onRestoreInstanceState(Parcelable state) { SavedState ss = (SavedState) state; super.onRestoreInstanceState(ss.getSuperState()); setDate(ss.year, ss.month, ss.day); updateSpinners(); updateCalendarView(); } /* (non-Javadoc) * @see android.view.View#onSaveInstanceState() */ @Override protected Parcelable onSaveInstanceState() { return new SavedState(super.onSaveInstanceState(), getYear(), getMonth(), getDayOfMonth()); } /** * Parses the date. * * @param date the date * @param outDate the out date * @return true, if successful */ private boolean parseDate(String date, Calendar outDate) { try { outDate.setTime(dateFormat.parse(date)); return true; } catch (ParseException e) { Log.w(DatePicker.LOG_TAG, "Date: " + date + " not in format: " + DatePicker.DATE_FORMAT); return false; } } /** * Push spinner. * * @param spinner the spinner * @param spinnerCount the spinner count * @param i the i */ private void pushSpinner(NumberPicker spinner, int spinnerCount, int i) { if (spinner.getParent() != null && spinner.getParent() instanceof ViewGroup) { ViewGroup parent = (ViewGroup) spinner.getParent(); if (parent.getChildAt(i) != spinner) { parent.removeView(spinner); parent.addView(spinner); setImeOptions(spinner, spinnerCount, i); } } } /** * Reorder spinners. */ private void reorderSpinners() { char[] order = DateFormat.getDateFormatOrder(getContext()); final int spinnerCount = order.length; for (int i = 0; i < spinnerCount; i++) { switch (order[i]) { case DateFormat.DATE: pushSpinner(daySpinner, spinnerCount, i); break; case DateFormat.MONTH: pushSpinner(monthSpinner, spinnerCount, i); break; case DateFormat.YEAR: pushSpinner(yearSpinner, spinnerCount, i); break; } } } /** * Sets the calendar view shown. * * @param shown the new calendar view shown */ public void setCalendarViewShown(boolean shown) { mCalendarView.setVisibility(shown ? View.VISIBLE : View.GONE); } /** * Sets the content descriptions. */ private void setContentDescriptions() { DatePicker.setContentDescription(daySpinner, R.id.increment, R.string.date_picker_increment_day_button); DatePicker.setContentDescription(daySpinner, R.id.decrement, R.string.date_picker_decrement_day_button); DatePicker.setContentDescription(monthSpinner, R.id.increment, R.string.date_picker_increment_month_button); DatePicker.setContentDescription(monthSpinner, R.id.decrement, R.string.date_picker_decrement_month_button); DatePicker.setContentDescription(yearSpinner, R.id.increment, R.string.date_picker_increment_year_button); DatePicker.setContentDescription(yearSpinner, R.id.decrement, R.string.date_picker_decrement_year_button); } /** * Sets the date. * * @param year the year * @param month the month * @param dayOfMonth the day of month */ private void setDate(int year, int month, int dayOfMonth) { currentDate.set(year, month, dayOfMonth); if (currentDate.before(minDate)) { currentDate.setTimeInMillis(minDate.getTimeInMillis()); } else if (currentDate.after(maxDate)) { currentDate.setTimeInMillis(maxDate.getTimeInMillis()); } } /* (non-Javadoc) * @see android.view.View#setEnabled(boolean) */ @Override public void setEnabled(boolean enabled) { if (isEnabled() == enabled) { return; } super.setEnabled(enabled); daySpinner.setEnabled(enabled); monthSpinner.setEnabled(enabled); yearSpinner.setEnabled(enabled); mCalendarView.setEnabled(enabled); } /** * Sets the ime options. * * @param spinner the spinner * @param spinnerCount the spinner count * @param spinnerIndex the spinner index */ private void setImeOptions(NumberPicker spinner, int spinnerCount, int spinnerIndex) { final int imeOptions; if (spinnerIndex < spinnerCount - 1) { imeOptions = EditorInfo.IME_ACTION_NEXT; } else { imeOptions = EditorInfo.IME_ACTION_DONE; } spinner.getInputField().setImeOptions(imeOptions); } /** * Sets the locale. * * @param locale the new locale */ @SuppressWarnings("deprecation") public void setLocale(Locale locale) { if (locale == null || locale.equals(this.locale)) { return; } this.locale = locale; tempDate = DatePicker.getCalendarForLocale(tempDate, locale); minDate = DatePicker.getCalendarForLocale(minDate, locale); maxDate = DatePicker.getCalendarForLocale(maxDate, locale); currentDate = DatePicker.getCalendarForLocale(currentDate, locale); numberOfMonths = tempDate.getActualMaximum(Calendar.MONTH) + 1; shortMonths = new String[numberOfMonths]; for (int i = 0; i < numberOfMonths; i++) { shortMonths[i] = DateUtils.getMonthString(Calendar.JANUARY + i, DateUtils.LENGTH_MEDIUM); } } /** * Sets the max date. * * @param maxDateL the new max date */ public void setMaxDate(long maxDateL) { tempDate.setTimeInMillis(maxDateL); if (tempDate.get(Calendar.YEAR) == maxDate.get(Calendar.YEAR) && tempDate.get(Calendar.DAY_OF_YEAR) == maxDate .get(Calendar.DAY_OF_YEAR)) { return; } maxDate.setTimeInMillis(maxDateL); mCalendarView.setMaxDate(maxDateL); if (currentDate.after(maxDate)) { currentDate.setTimeInMillis(maxDate.getTimeInMillis()); updateCalendarView(); } updateSpinners(); } /** * Sets the min date. * * @param minDateL the new min date */ public void setMinDate(long minDateL) { tempDate.setTimeInMillis(minDateL); if (tempDate.get(Calendar.YEAR) == minDate.get(Calendar.YEAR) && tempDate.get(Calendar.DAY_OF_YEAR) == minDate .get(Calendar.DAY_OF_YEAR)) { return; } minDate.setTimeInMillis(minDateL); mCalendarView.setMinDate(minDateL); if (currentDate.before(minDate)) { currentDate.setTimeInMillis(minDate.getTimeInMillis()); updateCalendarView(); } updateSpinners(); } /** * Sets the on date changed listener. * * @param onDateChangedListener the new on date changed listener */ public void setOnDateChangedListener( OnDateChangedListener onDateChangedListener) { this.onDateChangedListener = onDateChangedListener; } /** * Sets the spinners shown. * * @param shown the new spinners shown */ public void setSpinnersShown(boolean shown) { spinners.setVisibility(shown ? View.VISIBLE : View.GONE); } /** * Update calendar view. */ private void updateCalendarView() { mCalendarView.setDate(currentDate.getTimeInMillis(), false, false); } /** * Update date. * * @param year the year * @param month the month * @param dayOfMonth the day of month */ public void updateDate(int year, int month, int dayOfMonth) { if (!isNewDate(year, month, dayOfMonth)) { return; } setDate(year, month, dayOfMonth); updateSpinners(); updateCalendarView(); notifyDateChanged(); } /** * Update input state. */ private void updateInputState() { if (inputMethodManager != null) { checkInputState(yearSpinner, monthSpinner, daySpinner); } } /** * Update spinners. */ private void updateSpinners() { monthSpinner.setDisplayedValues(null); if (currentDate.equals(minDate)) { daySpinner.setMinValue(currentDate.get(Calendar.DAY_OF_MONTH)); daySpinner.setMaxValue(currentDate .getActualMaximum(Calendar.DAY_OF_MONTH)); daySpinner.setWrapSelectorWheel(false); monthSpinner.setMinValue(currentDate.get(Calendar.MONTH)); monthSpinner.setMaxValue(currentDate .getActualMaximum(Calendar.MONTH)); monthSpinner.setWrapSelectorWheel(false); } else if (currentDate.equals(maxDate)) { daySpinner.setMinValue(currentDate .getActualMinimum(Calendar.DAY_OF_MONTH)); daySpinner.setMaxValue(currentDate.get(Calendar.DAY_OF_MONTH)); daySpinner.setWrapSelectorWheel(false); monthSpinner.setMinValue(currentDate .getActualMinimum(Calendar.MONTH)); monthSpinner.setMaxValue(currentDate.get(Calendar.MONTH)); monthSpinner.setWrapSelectorWheel(false); } else { daySpinner.setMinValue(1); daySpinner.setMaxValue(currentDate .getActualMaximum(Calendar.DAY_OF_MONTH)); daySpinner.setWrapSelectorWheel(true); monthSpinner.setMinValue(0); monthSpinner.setMaxValue(11); monthSpinner.setWrapSelectorWheel(true); } String[] displayedValues = Arrays.copyOfRange(shortMonths, monthSpinner.getMinValue(), monthSpinner.getMaxValue() + 1); monthSpinner.setDisplayedValues(displayedValues); yearSpinner.setMinValue(minDate.get(Calendar.YEAR)); yearSpinner.setMaxValue(maxDate.get(Calendar.YEAR)); yearSpinner.setWrapSelectorWheel(false); yearSpinner.setValue(currentDate.get(Calendar.YEAR)); monthSpinner.setValue(currentDate.get(Calendar.MONTH)); daySpinner.setValue(currentDate.get(Calendar.DAY_OF_MONTH)); } }