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 }