001    /*
002     * Copyright 2005 Sun Microsystems, Inc., 4150 Network Circle,
003     * Santa Clara, California 95054, U.S.A. All rights reserved.
004     *
005     * This library is free software; you can redistribute it and/or
006     * modify it under the terms of the GNU Lesser General Public
007     * License as published by the Free Software Foundation; either
008     * version 2.1 of the License, or (at your option) any later version.
009     *
010     * This library is distributed in the hope that it will be useful,
011     * but WITHOUT ANY WARRANTY; without even the implied warranty of
012     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013     * Lesser General Public License for more details.
014     *
015     * You should have received a copy of the GNU Lesser General Public
016     * License along with this library; if not, write to the Free Software
017     * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
018     */
019    package org.jdesktop.swingx.plaf.basic;
020    
021    import org.jdesktop.swingx.JXDatePicker;
022    import org.jdesktop.swingx.JXDatePickerFormatter;
023    import org.jdesktop.swingx.calendar.DateSpan;
024    import org.jdesktop.swingx.calendar.JXMonthView;
025    import org.jdesktop.swingx.plaf.DatePickerUI;
026    
027    import javax.swing.*;
028    import javax.swing.plaf.ComponentUI;
029    import javax.swing.plaf.UIResource;
030    import javax.swing.text.View;
031    import java.awt.*;
032    import java.awt.event.*;
033    import java.beans.PropertyChangeEvent;
034    import java.beans.PropertyChangeListener;
035    import java.util.Date;
036    
037    /**
038     * @author Joshua Outwater
039     */
040    public class BasicDatePickerUI extends DatePickerUI {
041        protected JXDatePicker datePicker;
042        private JButton popupButton;
043        private BasicDatePickerPopup popup;
044        private Handler handler;
045        protected PropertyChangeListener propertyChangeListener;
046        protected MouseListener mouseListener;
047        protected MouseMotionListener mouseMotionListener;
048    
049        public static ComponentUI createUI(JComponent c) {
050            return new BasicDatePickerUI();
051        }
052    
053        @Override
054        public void installUI(JComponent c) {
055            datePicker = (JXDatePicker)c;
056            datePicker.setLayout(createLayoutManager());
057            installComponents();
058            installDefaults();
059            installKeyboardActions();
060            installListeners();
061        }
062    
063        @Override
064        public void uninstallUI(JComponent c) {
065            uninstallListeners();
066            uninstallKeyboardActions();
067            uninstallDefaults();
068            uninstallComponents();
069            datePicker.setLayout(null);
070            datePicker = null;
071        }
072    
073        protected void installComponents() {
074            JFormattedTextField editor = datePicker.getEditor();
075            if (editor == null || editor instanceof UIResource) {
076                datePicker.setEditor(createEditor());
077            }
078            datePicker.add(datePicker.getEditor());
079    
080            popupButton = createPopupButton();
081    
082            if (popupButton != null) {
083                // this is a trick to get hold of the client prop which
084                // prevents closing of the popup
085                JComboBox box = new JComboBox();
086                Object preventHide = box.getClientProperty("doNotCancelPopup");
087                popupButton.putClientProperty("doNotCancelPopup", preventHide);
088                datePicker.add(popupButton);
089            }
090        }
091    
092        protected void uninstallComponents() {
093            datePicker.remove(datePicker.getEditor());
094    
095            if (popupButton != null) {
096                datePicker.remove(popupButton);
097                popupButton = null;
098            }
099        }
100    
101        protected void installDefaults() {
102    
103        }
104    
105        protected void uninstallDefaults() {
106    
107        }
108    
109        protected void installKeyboardActions() {
110            KeyStroke enterKey =
111                    KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, false);
112    
113            JFormattedTextField editor = datePicker.getEditor();
114            InputMap inputMap = editor.getInputMap(JComponent.WHEN_FOCUSED);
115            inputMap.put(enterKey, "COMMIT_EDIT");
116    
117            ActionMap actionMap = editor.getActionMap();
118            actionMap.put("COMMIT_EDIT", new CommitEditAction());
119    
120            KeyStroke spaceKey =
121                    KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0, false);
122    
123            inputMap = popupButton.getInputMap(JComponent.WHEN_FOCUSED);
124            inputMap.put(spaceKey, "TOGGLE_POPUP");
125    
126            actionMap = popupButton.getActionMap();
127            actionMap.put("TOGGLE_POPUP", new TogglePopupAction());
128    
129        }
130    
131        protected void uninstallKeyboardActions() {
132    
133        }
134    
135        protected void installListeners() {
136            propertyChangeListener = createPropertyChangeListener();
137            mouseListener = createMouseListener();
138            mouseMotionListener = createMouseMotionListener();
139    
140            datePicker.addPropertyChangeListener(propertyChangeListener);
141    
142            if (popupButton != null) {
143                popupButton.addPropertyChangeListener(propertyChangeListener);
144                popupButton.addMouseListener(mouseListener);
145                popupButton.addMouseMotionListener(mouseMotionListener);
146            }
147    
148        }
149    
150        protected void uninstallListeners() {
151            datePicker.removePropertyChangeListener(propertyChangeListener);
152    
153            if (popupButton != null) {
154                popupButton.removePropertyChangeListener(propertyChangeListener);
155                popupButton.removeMouseListener(mouseListener);
156                popupButton.removeMouseMotionListener(mouseMotionListener);
157            }
158    
159            propertyChangeListener = null;
160            mouseListener = null;
161            mouseMotionListener = null;
162            handler = null;
163        }
164    
165        private Handler getHandler() {
166            if (handler == null) {
167                handler = new Handler();
168            }
169            return handler;
170        }
171    
172        protected PropertyChangeListener createPropertyChangeListener() {
173            return getHandler();
174        }
175    
176        protected LayoutManager createLayoutManager() {
177            return getHandler();
178        }
179    
180        protected MouseListener createMouseListener() {
181            return getHandler();
182        }
183    
184        protected MouseMotionListener createMouseMotionListener() {
185            return getHandler();
186        }
187    
188        /**
189         * Creates the editor used to edit the date selection.  Subclasses should
190         * override this method if they want to substitute in their own editor.
191         *
192         * @return an instance of a JFormattedTextField
193         */
194        protected JFormattedTextField createEditor() {
195            JFormattedTextField f = new DefaultEditor(new JXDatePickerFormatter());
196            f.setName("dateField");
197            f.setColumns(UIManager.getInt("JXDatePicker.numColumns"));
198            f.setBorder(UIManager.getBorder("JXDatePicker.border"));
199    
200            return f;
201        }
202    
203        protected JButton createPopupButton() {
204            JButton b = new JButton();
205            b.setName("popupButton");
206            b.setRolloverEnabled(false);
207            b.setMargin(new Insets(0, 3, 0, 3));
208    
209            Icon icon = UIManager.getIcon("JXDatePicker.arrowDown.image");
210            if (icon == null) {
211                icon = (Icon)UIManager.get("Tree.expandedIcon");
212            }
213            b.setIcon(icon);
214    
215            return b;
216        }
217    
218        /**
219         * {@inheritDoc}
220         */
221        @Override
222        public Dimension getMinimumSize(JComponent c) {
223            return getPreferredSize(c);
224        }
225    
226        /**
227         * {@inheritDoc}
228         */
229        @Override
230        public Dimension getPreferredSize(JComponent c) {
231            Dimension dim = datePicker.getEditor().getPreferredSize();
232            if (popupButton != null) {
233                dim.width += popupButton.getPreferredSize().width;
234            }
235            Insets insets = datePicker.getInsets();
236            dim.width += insets.left + insets.right;
237            dim.height += insets.top + insets.bottom;
238            return (Dimension)dim.clone();
239        }
240    
241        @Override
242        public int getBaseline(int width, int height) {
243            JFormattedTextField editor = datePicker.getEditor();
244            View rootView = editor.getUI().getRootView(editor);
245            if (rootView.getViewCount() > 0) {
246                Insets insets = editor.getInsets();
247                Insets insetsOut = datePicker.getInsets();
248                int nh = height - insets.top - insets.bottom
249                        - insetsOut.top - insetsOut.bottom;
250                int y = insets.top + insetsOut.top;
251                View fieldView = rootView.getView(0);
252                int vspan = (int) fieldView.getPreferredSpan(View.Y_AXIS);
253                if (nh != vspan) {
254                    int slop = nh - vspan;
255                    y += slop / 2;
256                }
257                FontMetrics fm = editor.getFontMetrics(editor.getFont());
258                y += fm.getAscent();
259                return y;
260            }
261            return -1;
262        }
263    
264        /**
265         * Action used to commit the current value in the JFormattedTextField.
266         * This action is used by the keyboard bindings.
267         */
268        private class CommitEditAction extends AbstractAction {
269            public CommitEditAction() {
270                super("CommitEditPopup");
271            }
272    
273            public void actionPerformed(ActionEvent ev) {
274                try {
275                    JFormattedTextField editor = datePicker.getEditor();
276                    // Commit the current value.
277                    editor.commitEdit();
278    
279                    // Reformat the value according to the formatter.
280                    editor.setValue(editor.getValue());
281                    datePicker.postActionEvent();
282                } catch (java.text.ParseException ex) {
283                }
284            }
285        }
286    
287        /**
288         * Action used to commit the current value in the JFormattedTextField.
289         * This action is used by the keyboard bindings.
290         */
291        private class TogglePopupAction extends AbstractAction {
292            public TogglePopupAction() {
293                super("TogglePopup");
294            }
295    
296            public void actionPerformed(ActionEvent ev) {
297                handler.toggleShowPopup();
298            }
299        }
300    
301        private class DefaultEditor extends JFormattedTextField implements UIResource {
302            public DefaultEditor(AbstractFormatter formatter) {
303                super(formatter);
304            }
305        }
306    
307        /**
308         * Popup component that shows a JXMonthView component along with controlling
309         * buttons to allow traversal of the months.  Upon selection of a date the
310         * popup will automatically hide itself and enter the selection into the
311         * editable field of the JXDatePicker.
312         */
313        protected class BasicDatePickerPopup extends JPopupMenu
314                implements ActionListener {
315    
316            public BasicDatePickerPopup() {
317                JXMonthView monthView = datePicker.getMonthView();
318                monthView.setActionCommand("MONTH_VIEW");
319                monthView.addActionListener(this);
320    
321                setLayout(new BorderLayout());
322                add(monthView, BorderLayout.CENTER);
323                JPanel linkPanel = datePicker.getLinkPanel();
324                if (linkPanel != null) {
325                    add(linkPanel, BorderLayout.SOUTH);
326                }
327            }
328    
329            public void actionPerformed(ActionEvent ev) {
330                String command = ev.getActionCommand();
331                if ("MONTH_VIEW".equals(command)) {
332                    DateSpan span = datePicker.getMonthView().getSelectedDateSpan();
333                    datePicker.getEditor().setValue(span.getStartAsDate());
334                    setVisible(false);
335                    datePicker.postActionEvent();
336                }
337            }
338        }
339    
340    
341        private class Handler implements LayoutManager, MouseListener, MouseMotionListener,
342                PropertyChangeListener {
343            private boolean _forwardReleaseEvent = false;
344    
345            public void mouseClicked(MouseEvent ev) {
346            }
347    
348            public void mousePressed(MouseEvent ev) {
349                if (!datePicker.isEnabled()) {
350                    return;
351                }
352    
353                if (!datePicker.isEditable()) {
354                    JFormattedTextField editor = datePicker.getEditor();
355                    if (editor.isEditValid()) {
356                        try {
357                            editor.commitEdit();
358                        } catch (java.text.ParseException ex) {
359                        }
360                    }
361                }
362                toggleShowPopup();
363            }
364    
365            public void mouseReleased(MouseEvent ev) {
366                if (!datePicker.isEnabled() || !datePicker.isEditable()) {
367                    return;
368                }
369    
370                // Retarget mouse event to the month view.
371                if (_forwardReleaseEvent) {
372                    JXMonthView monthView = datePicker.getMonthView();
373                    ev = SwingUtilities.convertMouseEvent(popupButton, ev,
374                            monthView);
375                    monthView.dispatchEvent(ev);
376                    _forwardReleaseEvent = false;
377                }
378            }
379    
380            public void mouseEntered(MouseEvent ev) {
381            }
382    
383            public void mouseExited(MouseEvent ev) {
384            }
385    
386            public void mouseDragged(MouseEvent ev) {
387                if (!datePicker.isEnabled() || !datePicker.isEditable()) {
388                    return;
389                }
390    
391                _forwardReleaseEvent = true;
392    
393                if (!popup.isShowing()) {
394                    return;
395                }
396    
397                // Retarget mouse event to the month view.
398                JXMonthView monthView = datePicker.getMonthView();
399                ev = SwingUtilities.convertMouseEvent(popupButton, ev, monthView);
400                monthView.dispatchEvent(ev);
401            }
402    
403            public void mouseMoved(MouseEvent ev) {
404            }
405    
406            public void toggleShowPopup() {
407                if (popup == null) {
408                    popup = new BasicDatePickerPopup();
409                }
410                if (!popup.isVisible()) {
411                    JFormattedTextField editor = datePicker.getEditor();
412                    if (editor.getValue() == null) {
413                        editor.setValue(new Date(datePicker.getLinkDate()));
414                    }
415                    DateSpan span =
416                            new DateSpan((java.util.Date)editor.getValue(),
417                                    (java.util.Date)editor.getValue());
418                    JXMonthView monthView = datePicker.getMonthView();
419                    monthView.setSelectedDateSpan(span);
420                    monthView.ensureDateVisible(
421                            ((Date)editor.getValue()).getTime());
422                    popup.show(datePicker,
423                            0, datePicker.getHeight());
424                } else {
425                    popup.setVisible(false);
426                }
427            }
428    
429            public void propertyChange(PropertyChangeEvent e) {
430                String property = e.getPropertyName();
431                if ("enabled".equals(property)) {
432                    boolean isEnabled = datePicker.isEnabled();
433                    popupButton.setEnabled(isEnabled);
434                    datePicker.getEditor().setEnabled(isEnabled);
435                } else if ("editable".equals(property)) {
436                    boolean isEditable = datePicker.isEditable();
437                    datePicker.getMonthView().setEnabled(isEditable);
438                    datePicker.getEditor().setEditable(isEditable);
439                } else if (JComponent.TOOL_TIP_TEXT_KEY.equals(property)) {
440                    String tip = datePicker.getToolTipText();
441                    datePicker.getEditor().setToolTipText(tip);
442                    popupButton.setToolTipText(tip);
443                } else if (JXDatePicker.MONTH_VIEW.equals(property)) {
444                    popup = null;
445                } else if (JXDatePicker.LINK_PANEL.equals(property)) {
446                    // If the popup is null we haven't shown it yet.
447                    JPanel linkPanel = datePicker.getLinkPanel();
448                    if (popup != null) {
449                        popup.remove(linkPanel);
450                        popup.add(linkPanel, BorderLayout.SOUTH);
451                    }
452                } else if (JXDatePicker.EDITOR.equals(property)) {
453                    JFormattedTextField oldEditor = (JFormattedTextField)e.getOldValue();
454                    if (oldEditor != null) {
455                        datePicker.remove(oldEditor);
456                    }
457    
458                    JFormattedTextField editor = (JFormattedTextField)e.getNewValue();
459                    datePicker.add(editor);
460                    datePicker.revalidate();
461                } else if ("componentOrientation".equals(property)) {
462                    datePicker.revalidate();
463                }
464            }
465    
466            public void addLayoutComponent(String name, Component comp) { }
467    
468            public void removeLayoutComponent(Component comp) { }
469    
470            public Dimension preferredLayoutSize(Container parent) {
471                return parent.getPreferredSize();
472            }
473    
474            public Dimension minimumLayoutSize(Container parent) {
475                return parent.getMinimumSize();
476            }
477    
478            public void layoutContainer(Container parent) {
479                Insets insets = datePicker.getInsets();
480                int width = datePicker.getWidth() - insets.left - insets.right;
481                int height = datePicker.getHeight() - insets.top - insets.bottom;
482    
483                int popupButtonWidth = popupButton != null ? popupButton.getPreferredSize().width : 0;
484    
485                boolean ltr = datePicker.getComponentOrientation().isLeftToRight();
486    
487                datePicker.getEditor().setBounds(ltr ? insets.left : insets.left + popupButtonWidth,
488                        insets.top,
489                        width - popupButtonWidth,
490                        height);
491    
492                if (popupButton != null) {
493                    popupButton.setBounds(ltr ? width - popupButtonWidth + insets.left : insets.left,
494                            insets.top,
495                            popupButtonWidth,
496                            height);
497                }
498            }
499        }
500    }