Package org.openquark.gems.client.valueentry

Source Code of org.openquark.gems.client.valueentry.RelativeDateValueEditor$RelativeDateValueEditorActionListener

/*
* Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
*     * Redistributions of source code must retain the above copyright notice,
*       this list of conditions and the following disclaimer.
*     * Redistributions in binary form must reproduce the above copyright
*       notice, this list of conditions and the following disclaimer in the
*       documentation and/or other materials provided with the distribution.
*     * Neither the name of Business Objects nor the names of its contributors
*       may be used to endorse or promote products derived from this software
*       without specific prior written permission.
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/


/*
* RelativeDateValueEditor.java
* Creation date: September 22, 2006
* By: Neil Corkum
*/
package org.openquark.gems.client.valueentry;

import java.awt.Component;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.text.DateFormatSymbols;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFormattedTextField;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.JToggleButton;
import javax.swing.SpinnerNumberModel;
import javax.swing.SwingConstants;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.text.DefaultFormatter;

import org.openquark.cal.valuenode.RelativeDateValueNode;
import org.openquark.cal.valuenode.ValueNode;

import com.ibm.icu.util.Calendar;
import com.ibm.icu.util.TimeZone;

/**
* A RelativeDateValueEditor displays a calendar-like window which allows the user
* to select a date.
*
* @author Neil Corkum
*/
class RelativeDateValueEditor extends ValueEditor {

    private static final long serialVersionUID = 8737584309664754353L;

    /**
     * A custom value editor provider for the RelativeDateValueEditor.
     */
    public static class RelativeDateValueEditorProvider extends ValueEditorProvider<RelativeDateValueEditor> {
        public RelativeDateValueEditorProvider(ValueEditorManager valueEditorManager) {
            super(valueEditorManager);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean canHandleValue(ValueNode valueNode, SupportInfo providerSupportInfo) {
            return valueNode instanceof RelativeDateValueNode;
        }
       
        /**
         * @see org.openquark.gems.client.valueentry.ValueEditorProvider#getEditorInstance(ValueEditorHierarchyManager, ValueNode)
         */
        @Override
        public RelativeDateValueEditor getEditorInstance(ValueEditorHierarchyManager valueEditorHierarchyManager,
                                             ValueNode valueNode) {
           
            RelativeDateValueEditor editor = new RelativeDateValueEditor(valueEditorHierarchyManager);
            editor.setOwnerValueNode(valueNode);
            return editor;
        }
       
        /**
         * {@inheritDoc}
         */
        @Override
        public boolean usableForOutput() {
            return false;
        }
    }
   
    /**
     * Key listener for the buttons in this value editor.
     * Allows use of keyboard arrows to move around the date portion of
     * the calendar.
     * Gives the enter key the same effect as the space bar (presses current button).
     *
     *
     * @author Neil Corkum
     */
    private class RelativeDateValueEditorKeyListener extends ValueEditorKeyListener {
        @Override
        public void keyPressed(KeyEvent evt) {
            int keyCode = evt.getKeyCode();
        
            // check for escape pressed
            if (keyCode == KeyEvent.VK_ESCAPE) {
                // close editor without saving changes, unless month selector
                // popup menu is open, in which case do nothing (the combo box
                // will handle closing the popup menu
                if (!monthSelector.isPopupVisible()) {
                    handleCancelGesture();
                    evt.consume();
                }
                return;
            }
           
            // if enter pressed exit and save changes
            if (keyCode == KeyEvent.VK_ENTER) {
                handleCommitGesture();
                evt.consume();
                return;
            }
                     
            Component component = evt.getComponent();
           
            if (component instanceof JToggleButton) {
                JToggleButton button = (JToggleButton)component;

                int deltaDays; // difference between currently focused day and new day

                // check for arrow keys pressed
                if (keyCode == KeyEvent.VK_DOWN) {
                    deltaDays = DAYS_IN_WEEK;                  
                } else if (keyCode == KeyEvent.VK_UP) {
                    deltaDays = -DAYS_IN_WEEK;
                } else if (keyCode == KeyEvent.VK_LEFT) {
                    deltaDays = -1;
                } else if (keyCode == KeyEvent.VK_RIGHT) {
                    deltaDays = 1;
                } else {
                    // no relevant key pressed; do nothing
                    return;
                }
               
                // We must get the currently focused button's date, then convert
                // to a Calendar and increment/decrement the Calendar by a certain
                // number of days. The new day is then obtained from the calendar.
                // Just using an integer and adding/subtracting will not work for
                // a month in which each day is not one greater than the day before
                // it (eg. October 1582, in the Gregorian calendar)
                int day = dayButtons.indexOf(button) + 1;
                Calendar calendar = getUtcCalendar();
                calendar.set(getDisplayedYear(), getDisplayedMonth(), day);
               
                // Add the day delta.  Do nothing if it changes the month.
                calendar.add(Calendar.DAY_OF_MONTH, deltaDays);
               
                if (calendar.get(Calendar.MONTH) == getDisplayedMonth()) {
                    int newDay = calendar.get(Calendar.DAY_OF_MONTH);
                    JToggleButton newFocus = dayButtons.get(newDay - 1);
                    newFocus.doClick(0);
                }
            }
        }
    }
   
    /**
     * Listener for action events from the date buttons and the month selector
     * combo box.
     *
     * @author Neil Corkum
     */
    private class RelativeDateValueEditorActionListener implements ActionListener {
        /**
         * {@inheritDoc}
         */
        public void actionPerformed(ActionEvent e) {
            // check if month changed
            if (e.getActionCommand().equals(monthSelector.getActionCommand())) {
                // update selected date so that it is in the month selected
                // use roll instead of set because roll handles going from
                // May 31 to June 30, for example, while set would go to July 1
                int oldMonth = selectedDate.get(Calendar.MONTH);
                selectedDate.roll(Calendar.MONTH, getDisplayedMonth() - oldMonth);
 
                updateCalendar();
            } else {
                // date was changed
                // The action command from a button is a string representing
                // the date the button shows.
                // Get this date and use it to set the new date.
                try {
                    int dateSelected = Integer.parseInt(e.getActionCommand());
                    Calendar newDate = getUtcCalendar();
                    newDate.set(getDisplayedYear(), getDisplayedMonth(), dateSelected);
                    setCalendar(newDate);           
                } catch (NumberFormatException exc) {
               
                }
            }
        }
    }
   
    /**
     * Listener for change events from the year selector combo box.
     * @author Neil Corkum
     */
    private class RelativeDateValueEditorChangeListener implements ChangeListener {
        /**
         * {@inheritDoc}
         */
        public void stateChanged(ChangeEvent e) {
            // update selected date so that it is in the new selected year
            // use add instead of set because add handles going from
            // Feb 29 one leap year to Feb 28 the next year, whereas set will
            // say March 1
            int oldYear = selectedDate.get(Calendar.YEAR);
            selectedDate.add(Calendar.YEAR, getDisplayedYear() - oldYear);

            updateCalendar();
        }
    }

    /** Minimum year displayable and selectable in editor. */
    private static final int MINIMUM_YEAR = 1;
   
    /** Days in week */
    private static final int DAYS_IN_WEEK = 7;
   
    /** Months in year */
    private static final int MONTHS_IN_YEAR = 12;

    /** Combo box holding list of months. */
    private JComboBox monthSelector;
   
    /** Spinner field displaying year. */
    private JSpinner yearSelector;
   
    /** List of buttons usable in UI. */
    private List <JToggleButton> dayButtons;
   
    /** Panel holding layout of buttons representing days in month. */
    private JPanel calendarPanel;
   
    /** Date currently selected. */
    private Calendar selectedDate;
   
    /**
     * Constructor for RelativeDateValueEditor.
     * @param valueEditorHierarchyManager
     */
    protected RelativeDateValueEditor(ValueEditorHierarchyManager valueEditorHierarchyManager) {
        super(valueEditorHierarchyManager);
        initialize();
    }
   
    /**
     * {@inheritDoc}
     */
    @Override
    protected void commitValue() {
       
        // Update the value in the ValueNode.
        ValueNode returnVN = new RelativeDateValueNode(getCalendar().getTime(), getValueNode().getTypeExpr());
        replaceValueNode(returnVN, false);

        notifyValueCommitted();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Component getDefaultFocusComponent() {
        return monthSelector;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setInitialValue() {
        Calendar calendar = ((RelativeDateValueNode)getValueNode()).getCalendarValue();
        setCalendar(calendar);
    }
   
    /**
     * Function to initialize the editor.
     */
    private void initialize() {
        // set locale
        // TODO: get locale from GemCutter instead of using default
        Locale locale = Locale.getDefault();
        setLocale(locale);
       
        // create listeners
        RelativeDateValueEditorKeyListener keyListener = new RelativeDateValueEditorKeyListener();
        RelativeDateValueEditorActionListener actionListener = new RelativeDateValueEditorActionListener();
        RelativeDateValueEditorChangeListener changeListener = new RelativeDateValueEditorChangeListener();
       
        selectedDate = getUtcCalendar()// set to current date
        int maxDaysInMonth = selectedDate.getMaximum(Calendar.DAY_OF_MONTH);
       
        // set up year selector spinner field
        SpinnerNumberModel numberModel = new SpinnerNumberModel();
        numberModel.setMinimum(Integer.valueOf(MINIMUM_YEAR));
        numberModel.setMaximum(Integer.valueOf(selectedDate.getActualMaximum(Calendar.YEAR)));
        yearSelector = new JSpinner(numberModel);
        yearSelector.setLocale(locale);
        yearSelector.setEditor(new JSpinner.NumberEditor(yearSelector, "0"));
       
        // set up month selector field
        monthSelector = new JComboBox(getMonthStrings());
        monthSelector.setLocale(locale);

        // initialize buttons for all possible days in month
        dayButtons = new ArrayList<JToggleButton>(maxDaysInMonth);
        for (int day = 1; day <= maxDaysInMonth; day++) {
            JToggleButton button = new JToggleButton(getDayString(day));
            button.setLocale(locale);
           
            // set buttons' action command to a string containing the date they represent
            button.setActionCommand(String.valueOf(day));
            button.addActionListener(actionListener);
            button.addKeyListener(keyListener);
            dayButtons.add(button);
        }
       
        // initialize panels
        JPanel monthPanel = new JPanel();
        JPanel yearPanel = new JPanel();
        JPanel weekdayLabelPanel = new JPanel();
        calendarPanel = new JPanel();

        // set up month panel
        monthPanel.setLayout(new BoxLayout(monthPanel, BoxLayout.X_AXIS));
        monthPanel.add(monthSelector);
       
        // set up year panel
        yearPanel.setLayout(new BoxLayout(yearPanel, BoxLayout.X_AXIS));
        yearPanel.add(yearSelector);
       
        // set up calendar panel
        GridLayout calendarLayout = new GridLayout(0, DAYS_IN_WEEK);
        calendarPanel.setLayout(calendarLayout);  
       
        // set up weekday label panel
        weekdayLabelPanel.setLayout(new GridLayout(1, DAYS_IN_WEEK));
        String[] weekdayStrings = getWeekdayStrings();
        for (int i = 0; i < DAYS_IN_WEEK; i++) {
            JLabel weekday = new JLabel(weekdayStrings[i], SwingConstants.CENTER);
            weekday.setLocale(locale);
            weekdayLabelPanel.add(weekday);
        }
       
        // set up main panel
        GridBagLayout gridBag = new GridBagLayout();
        GridBagConstraints constraints = new GridBagConstraints();
       
        setLayout(gridBag);
       
        constraints.fill = GridBagConstraints.BOTH;
        constraints.weightx = 1.0;

        gridBag.addLayoutComponent(monthPanel, constraints);
        add(monthPanel);
       
        constraints.gridwidth = GridBagConstraints.REMAINDER;
        gridBag.addLayoutComponent(yearPanel, constraints);
        add(yearPanel);
       
        gridBag.addLayoutComponent(weekdayLabelPanel, constraints);
        add(weekdayLabelPanel);
       
        gridBag.addLayoutComponent(calendarPanel, constraints);
        add(calendarPanel);  
       
        // set up calendar to display current date
        setDisplayedMonth(selectedDate.get(Calendar.MONTH));
        setDisplayedYear(selectedDate.get(Calendar.YEAR));
        updateCalendar();
       
        // set up listeners
        monthSelector.addActionListener(actionListener);
        monthSelector.addKeyListener(keyListener);
       
        yearSelector.addChangeListener(changeListener);
       
        JComponent yearEditor = yearSelector.getEditor();
        if (yearEditor instanceof JSpinner.DefaultEditor) {
            final JFormattedTextField yearTextField = ((JSpinner.DefaultEditor)yearEditor).getTextField();
            // add listener to text field of year selector spinner
            yearTextField.addKeyListener(keyListener);
           
            // listener to select year text when spinner is "spun"
            PropertyChangeListener spinListener = new PropertyChangeListener() {
                public void propertyChange(PropertyChangeEvent e) {
                    yearTextField.selectAll();
                }
            };
            yearTextField.addPropertyChangeListener("value", spinListener);
           
            // make calendar update whenever an edit is made to the year selection
            JFormattedTextField.AbstractFormatter formatter = yearTextField.getFormatter();
            if (formatter instanceof DefaultFormatter) {
                DefaultFormatter defaultFormatter = (DefaultFormatter)formatter;
                defaultFormatter.setCommitsOnValidEdit(true);
                defaultFormatter.setAllowsInvalid(false);
            }
        }
       
        // set visible size of this editor
        setSize(getPreferredSize());
    }

    /**
     * Gets the names of all months in the locale.
     * @return array of strings representing each month
     */
    private String[] getMonthStrings() {
        String[] months = new String[MONTHS_IN_YEAR];
        String[] allMonths = new DateFormatSymbols(getLocale()).getMonths();
        for (int month = 0; month < MONTHS_IN_YEAR; month++) {
            months[month] = allMonths[month];
        }
        return months;
    }
   
    /**
     * Gets the names of all weekdays in the locale.
     * The names are ordered in the correct order for the locale.
     * @return array of weekday names
     */
    private String[] getWeekdayStrings() {
        String[] orderedNames = new String[DAYS_IN_WEEK];
        String[] names = new DateFormatSymbols(getLocale()).getShortWeekdays();
       
        // set day names in proper order for locale
        int day = selectedDate.getFirstDayOfWeek();
        for (int i = 0; i < DAYS_IN_WEEK; i++) {
            orderedNames[i] = names[day];
            day = (day % DAYS_IN_WEEK) + 1; // day value is 1-based
        }
       
        return orderedNames;
    }
   
    /**
     * Gets a string representing a given day of the month
     * @param date int value of date
     * @return corresponding to date
     */
    private String getDayString(int date) {
        return String.valueOf(date);
    }
   
    /**
     * Updates calendar display and buttons to match month and year displayed.
     */
    private void updateCalendar() {
        calendarPanel.invalidate();
        calendarPanel.removeAll();
       
        Calendar firstDayOfMonth = getUtcCalendar();
       
        int displayedMonth = getDisplayedMonth();
        int displayedYear = getDisplayedYear();
        firstDayOfMonth.set(displayedYear, displayedMonth, 1);
        int firstDayOfWeek = firstDayOfMonth.getFirstDayOfWeek();    
        int firstDayOfWeekOfThisMonth = firstDayOfMonth.get(Calendar.DAY_OF_WEEK);
        List<Integer> daysInMonth = getDaysInMonth(displayedYear, displayedMonth);
       
        // calculate the number of blank spaces to leave at start of grid that will contain calendar
        int blankStartDays = firstDayOfWeekOfThisMonth - firstDayOfWeek;
        if (blankStartDays < 0) {
            blankStartDays += DAYS_IN_WEEK;
        }
       
        // insert blank sections in grid
        for (int i = 0; i < blankStartDays; i++) {
            calendarPanel.add(getCalendarSpacerComponent());
        }
       
        // Need calendar to validate its internal fields so that no dates past
        // the end of the "valid" range are accepted.
        // This is a grotesque hack
        // add method seems to make calendar validate itself
        selectedDate.add(Calendar.MILLISECOND, -1);
        selectedDate.add(Calendar.MILLISECOND, 1);

        // add buttons to grid
        for (final Integer dayInMonth : daysInMonth) {
            int date = dayInMonth.intValue();
            int dateSelected = selectedDate.get(Calendar.DAY_OF_MONTH);
           
            JToggleButton button = (dayButtons.get(date - 1));
           
            // check for selected date button
            // set only the selected button to be focusable, this makes using
            // the TAB key to change focus work more intuitively
            if (date == dateSelected) {
                button.setSelected(true);
                button.setFocusable(true);
                button.setText("<html><b><u>" + getDayString(date) + "</u></b></html>");
            } else {
                button.setSelected(false);
                button.setFocusable(false);
                button.setText("<html>" + getDayString(date) + "</html>");
            }
            calendarPanel.add(button);
        }

        // add extra blank sections to ensure that grid is always 6 rows of dates
        final int necessaryGridComponents = DAYS_IN_WEEK * 5 + 1;
        for (int componentsInGrid = blankStartDays + daysInMonth.size();
             componentsInGrid < necessaryGridComponents;
             componentsInGrid++) {
            calendarPanel.add(getCalendarSpacerComponent());
        }

        // redraw calendar panel
        calendarPanel.validate();
        calendarPanel.repaint();
    }
   
    /**
     * Function to set the selected date
     */
    public void setCalendar(Calendar date) {
        selectedDate = date;
        setDisplayedMonth(date.get(Calendar.MONTH));
        setDisplayedYear(date.get(Calendar.YEAR));
        updateCalendar();
       
        // give focus to selected date button
        int day = selectedDate.get(Calendar.DAY_OF_MONTH);
        JToggleButton button = dayButtons.get(day - 1);
        button.requestFocus();
    }
   
    /**
     * Gets the month displayed
     * @return the month currently displayed in the window
     */
    private int getDisplayedMonth() {
        return monthSelector.getSelectedIndex();
    }
   
    /**
     * Gets the year displayed
     * @return the year currently displayed in the window
     */
    private int getDisplayedYear() {
        return ((Integer)yearSelector.getValue()).intValue();
    }
   
    /**
     * Sets the month displayed
     * @param month the month
     */
    private void setDisplayedMonth(int month) {
        monthSelector.setSelectedIndex(month);
    }
   
    /**
     * Sets the year displayed
     * @param year the year
     */
    private void setDisplayedYear(int year) {
        yearSelector.setValue(Integer.valueOf(year));
    }
   
    /**
     * Gets a list of days in the selected month.
     * @param year the year
     * @param month the month
     * @return List of dates in month
     */
    private List<Integer> getDaysInMonth(int year, int month) {
        // set up calendar to first day of month
        Calendar calendar = getUtcCalendar();
        calendar.clear();
        calendar.set(year, month, 1);
       
        ArrayList<Integer> dayList = new ArrayList<Integer>(31);
       
        // lastCalendar is used to check the situation where the calendar hits
        // its maximum value and can no longer be increased
        Calendar lastCalendar = (Calendar)calendar.clone();
        lastCalendar.add(Calendar.DAY_OF_MONTH, -1);
       
        // increase date and add to list until the next month is reached
        while ((calendar.get(Calendar.MONTH) == month) && !calendar.equals(lastCalendar)) {
           
            dayList.add(Integer.valueOf(calendar.get(Calendar.DAY_OF_MONTH)));
            calendar.add(Calendar.DAY_OF_MONTH, 1);
            lastCalendar.add(Calendar.DAY_OF_MONTH, 1);
        }
       
        return dayList;
    }
   
    /**
     * Gets the value of the selected date
     * @return the selected value in the editor
     */
    public Calendar getCalendar() {
        return selectedDate;
    }
   
    /**
     * Gets a Calendar instance with the current locale and UTC time zone.
     * @return Calendar instance
     */
    private Calendar getUtcCalendar() {
        return Calendar.getInstance(TimeZone.getTimeZone("UTC"), getLocale());
    }
   
    /**
     * Gets an instance of an invisible component that can be used as a spacer.
     * Used in the calendar grid to fill in the spaces before and after actual
     * days in the month.
     * @return Invisible spacer component
     */
    private Component getCalendarSpacerComponent() {
        Component glue = Box.createGlue();
        glue.setVisible(false);
        return glue;
    }
}
TOP

Related Classes of org.openquark.gems.client.valueentry.RelativeDateValueEditor$RelativeDateValueEditorActionListener

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.