001: /*--------------------------------------------------------------------------*
002: | Copyright (C) 2006 Christopher Kohlhaas |
003: | |
004: | This program is free software; you can redistribute it and/or modify |
005: | it under the terms of the GNU General Public License as published by the |
006: | Free Software Foundation. A copy of the license has been included with |
007: | these distribution in the COPYING file, if not go to www.fsf.org |
008: | |
009: | As a special exception, you are granted the permissions to link this |
010: | program with every library, which license fulfills the Open Source |
011: | Definition as published by the Open Source Initiative (OSI). |
012: *--------------------------------------------------------------------------*/
013:
014: package org.rapla.components.calendar;
015:
016: import java.awt.BorderLayout;
017: import java.awt.Color;
018: import java.awt.Component;
019: import java.awt.Dimension;
020: import java.awt.Font;
021: import java.awt.Graphics;
022: import java.awt.Image;
023: import java.awt.MediaTracker;
024: import java.awt.Point;
025: import java.awt.Toolkit;
026: import java.awt.event.ActionEvent;
027: import java.awt.event.ActionListener;
028: import java.awt.event.KeyEvent;
029: import java.awt.event.MouseEvent;
030: import java.awt.event.MouseListener;
031: import java.awt.event.MouseMotionListener;
032: import java.awt.image.BufferedImage;
033: import java.net.URL;
034: import java.text.DateFormat;
035: import java.util.ArrayList;
036: import java.util.Calendar;
037: import java.util.Collection;
038: import java.util.Date;
039: import java.util.Locale;
040: import java.util.TimeZone;
041:
042: import javax.swing.BorderFactory;
043: import javax.swing.DefaultListCellRenderer;
044: import javax.swing.DefaultListModel;
045: import javax.swing.ImageIcon;
046: import javax.swing.JComponent;
047: import javax.swing.JList;
048: import javax.swing.JPanel;
049: import javax.swing.JScrollBar;
050: import javax.swing.JScrollPane;
051: import javax.swing.ListSelectionModel;
052: import javax.swing.MenuElement;
053: import javax.swing.MenuSelectionManager;
054: import javax.swing.event.ChangeEvent;
055: import javax.swing.event.ChangeListener;
056:
057: /** A ComboBox like time chooser.
058: * It is localizable and it uses swing-components.
059: * <p>The combobox editor is a {@link TimeField}. If the ComboBox-Button
060: * is pressed a TimeSelectionList will drop down.</p>
061: * @author Christopher Kohlhaas
062: */
063: public final class RaplaTime extends RaplaComboBox {
064: private static final long serialVersionUID = 1L;
065:
066: protected TimeField m_timeField;
067: protected TimeList m_timeList;
068: protected TimeModel m_timeModel;
069: protected Collection m_listenerList = new ArrayList();
070: private Date m_lastTime;
071: private int m_visibleRowCount = -1;
072: private int m_rowsPerHour = 4;
073: private TimeRenderer m_renderer;
074: private static Image clock;
075: boolean m_showClock;
076:
077: /** Create a new TimeBox with the default locale. */
078: public RaplaTime() {
079: this (Locale.getDefault());
080: }
081:
082: /** Create a new TimeBox with the specified locale. */
083: public RaplaTime(Locale locale) {
084: this (locale, TimeZone.getDefault(), true, true);
085: }
086:
087: /** Create a new TimeBox with the specified locale and timeZone. */
088: public RaplaTime(Locale locale, TimeZone timeZone) {
089: this (locale, timeZone, true, true);
090: }
091:
092: /** @deprecated replaced with #RaplaTime(Locale,TimeZone,boolean) */
093: public RaplaTime(Locale locale, boolean isDropDown) {
094: this (locale, TimeZone.getDefault(), true, true);
095: }
096:
097: /** Create a new TimeBox with the specified locale and timeZone. The
098: isDropDown flag specifies if times could be selected
099: via a drop-down-box.
100: */
101: public RaplaTime(Locale locale, TimeZone timeZone,
102: boolean isDropDown, boolean showClock) {
103: super (isDropDown, new TimeField(locale, timeZone));
104: m_showClock = showClock;
105: m_timeModel = new TimeModel(locale, timeZone);
106: m_timeField = (TimeField) m_editorComponent;
107: Listener listener = new Listener();
108: m_timeField.addChangeListener(listener);
109: m_timeModel.addDateChangeListener(listener);
110: m_lastTime = m_timeModel.getTime();
111:
112: if (showClock) {
113: if (clock == null) {
114: URL url = RaplaTime.class.getResource("clock.png");
115: if (url != null) {
116: clock = Toolkit.getDefaultToolkit()
117: .createImage(url);
118: MediaTracker m = new MediaTracker(this );
119: m.addImage(clock, 0);
120: try {
121: m.waitForID(0);
122: } catch (InterruptedException ex) {
123: }
124: }
125: }
126:
127: getLabel().setIcon(new ImageIcon(createClockImage()));
128: getLabel().setBorder(
129: BorderFactory.createEmptyBorder(0, 0, 0, 1));
130: }
131:
132: }
133:
134: static Color HOUR_POINTER = new Color(40, 40, 100);
135: static Color MINUTE_POINTER = new Color(100, 100, 180);
136:
137: protected Image createClockImage() {
138: BufferedImage image = new BufferedImage(17, 17,
139: BufferedImage.TYPE_INT_ARGB);
140: Calendar calendar = Calendar.getInstance(getTimeZone(),
141: m_timeModel.getLocale());
142: calendar.setTime(m_timeModel.getTime());
143: int hourOfDay = calendar.get(Calendar.HOUR_OF_DAY) % 12;
144: int minute = calendar.get(Calendar.MINUTE);
145:
146: Graphics g = image.getGraphics();
147:
148: double hourPos = (hourOfDay * 60 + minute - 180) / (60.0 * 12)
149: * 2 * Math.PI;
150: double minutePos = (minute - 15) / 60.0 * 2 * Math.PI;
151: int xhour = (int) (Math.cos(hourPos) * 4.5);
152: int yhour = (int) (Math.sin(hourPos) * 4.5);
153: int xminute = (int) (Math.cos(minutePos) * 6.5);
154: int yminute = (int) (Math.sin(minutePos) * 6.5);
155: g.drawImage(clock, 0, 0, 17, 17, null);
156: g.setColor(HOUR_POINTER);
157: int centerx = 8;
158: int centery = 8;
159: g.drawLine(centerx, centery, centerx + xhour, centery + yhour);
160: g.setColor(MINUTE_POINTER);
161: g.drawLine(centerx, centery, centerx + xminute, centery
162: + yminute);
163: return image;
164: }
165:
166: /** The granularity of the selection rows:
167: * <ul>
168: * <li>1: 1 rows per hour = 1 Hour</li>
169: * <li>2: 2 rows per hour = 1/2 Hour</li>
170: * <li>2: 3 rows per hour = 20 Minutes</li>
171: * <li>4: 4 rows per hour = 15 Minutes</li>
172: * <li>6: 6 rows per hour = 10 Minutes</li>
173: * <li>12: 12 rows per hour = 5 Minutes</li>
174: * </ul>
175: */
176: public void setRowsPerHour(int rowsPerHour) {
177: m_rowsPerHour = rowsPerHour;
178: if (m_timeList != null) {
179: throw new IllegalStateException(
180: "Property can only be set during initialization.");
181: }
182: }
183:
184: /** @see #setRowsPerHour */
185: public int getRowsPerHour() {
186: return m_rowsPerHour;
187: }
188:
189: class Listener implements ChangeListener, DateChangeListener {
190: // Implementation of ChangeListener
191: public void stateChanged(ChangeEvent evt) {
192: validateEditor();
193: }
194:
195: public void dateChanged(DateChangeEvent evt) {
196: closePopup();
197: if (needSync())
198: m_timeField.setTime(evt.getDate());
199: if (m_lastTime == null || !m_lastTime.equals(evt.getDate()))
200: fireTimeChanged(evt.getDate());
201: m_lastTime = evt.getDate();
202: if (clock != null && m_showClock) {
203: getLabel().setIcon(new ImageIcon(createClockImage()));
204: }
205: }
206: }
207:
208: /** test if we need to synchronize the dateModel and the m_timeField*/
209: private boolean needSync() {
210: return (m_timeField.getTime() != null && !m_timeModel
211: .sameTime(m_timeField.getTime()));
212: }
213:
214: protected void validateEditor() {
215: if (needSync())
216: m_timeModel.setTime(m_timeField.getTime());
217: }
218:
219: /** the number of visble rows in the drop-down menu.*/
220: public void setVisibleRowCount(int count) {
221: m_visibleRowCount = count;
222: if (m_timeList != null)
223: m_timeList.getList().setVisibleRowCount(count);
224: }
225:
226: public void setFont(Font font) {
227: super .setFont(font);
228: // Method called during constructor?
229: if (m_timeList == null || font == null)
230: return;
231: m_timeList.setFont(font);
232: }
233:
234: public void setLocale(Locale locale) {
235: super .setLocale(locale);
236: if (m_timeField != null)
237: m_timeField.setLocale(locale);
238: }
239:
240: public void setTimeZone(TimeZone zone) {
241: m_timeModel.setTimeZone(zone);
242: m_timeField.setTimeZone(zone);
243: }
244:
245: public TimeZone getTimeZone() {
246: return m_timeField.getTimeZone();
247: }
248:
249: /** Set the time relative to the given timezone.
250: * The date,month and year values will be ignored.
251: */
252: public void setTime(Date time) {
253: m_timeModel.setTime(time);
254: }
255:
256: /** Parse this date with a calendar-object set to the correct
257: time-zone to get the hour,minute and second. The
258: date,month and year values should be ignored.
259: */
260: public Date getTime() {
261: return m_timeModel.getTime();
262: }
263:
264: protected void showPopup() {
265: validateEditor();
266: super .showPopup();
267: m_timeList.selectTime(m_timeField.getTime());
268: }
269:
270: /** registers new DateChangedListener for this component.
271: * An DateChangedEvent will be fired to every registered DateChangedListener
272: * when the a different time is selected.
273: * @see DateChangeListener
274: * @see DateChangeEvent
275: */
276: public void addDateChangeListener(DateChangeListener listener) {
277: m_listenerList.add(listener);
278: }
279:
280: /** removes a listener from this component.*/
281: public void removeDateChangeListener(DateChangeListener listener) {
282: m_listenerList.remove(listener);
283: }
284:
285: public DateChangeListener[] getDateChangeListeners() {
286: return (DateChangeListener[]) m_listenerList
287: .toArray(new DateChangeListener[] {});
288: }
289:
290: protected void fireTimeChanged(Date date) {
291: DateChangeListener[] listeners = getDateChangeListeners();
292: if (listeners.length == 0)
293: return;
294: DateChangeEvent evt = new DateChangeEvent(this , date);
295: for (int i = 0; i < listeners.length; i++) {
296: listeners[i].dateChanged(evt);
297: }
298: }
299:
300: /** The popup-component will be created lazily.*/
301: public JComponent getPopupComponent() {
302: if (m_timeList == null) {
303: m_timeList = new TimeList(m_rowsPerHour);
304: m_timeList.setModel(m_timeModel, m_timeField
305: .getOutputFormat());
306: m_timeList.setFont(getFont());
307: if (m_visibleRowCount >= 0)
308: m_timeList.getList().setVisibleRowCount(
309: m_visibleRowCount);
310: }
311: m_timeList.setTimeRenderer(m_renderer);
312: return m_timeList;
313: }
314:
315: public void setTimeRenderer(TimeRenderer renderer) {
316: m_renderer = renderer;
317: }
318:
319: }
320:
321: class TimeList extends JPanel implements MenuElement, MouseListener,
322: MouseMotionListener {
323: private static final long serialVersionUID = 1L;
324:
325: JScrollPane scrollPane = new JScrollPane();
326: NavButton upButton = new NavButton('^', 10);
327: NavButton downButton = new NavButton('v', 10);
328: JList m_list;
329: DateFormat m_format;
330: TimeModel m_timeModel;
331: int m_rowsPerHour = 4;
332: int m_minutesPerRow = 60 / m_rowsPerHour;;
333: TimeRenderer m_renderer;
334:
335: private double getRowHeight() {
336: return m_list.getVisibleRect().getHeight()
337: / m_list.getVisibleRowCount();
338: }
339:
340: public TimeList(int rowsPerHour) {
341: super ();
342: this .setLayout(new BorderLayout());
343: JPanel upPane = new JPanel();
344: upPane.setLayout(new BorderLayout());
345: upPane.add(upButton, BorderLayout.CENTER);
346: JPanel downPane = new JPanel();
347: downPane.setLayout(new BorderLayout());
348: downPane.add(downButton, BorderLayout.CENTER);
349: upButton.addActionListener(new ActionListener() {
350: public void actionPerformed(ActionEvent e) {
351: int direction = (int) -getRowHeight()
352: * (m_list.getVisibleRowCount() - 1);
353: JScrollBar bar = scrollPane.getVerticalScrollBar();
354: int value = Math.min(Math.max(0, bar.getValue()
355: + direction), bar.getMaximum());
356: scrollPane.getVerticalScrollBar().setValue(value);
357: }
358:
359: });
360: downButton.addActionListener(new ActionListener() {
361: public void actionPerformed(ActionEvent e) {
362: int direction = (int) getRowHeight()
363: * (m_list.getVisibleRowCount() - 1);
364: JScrollBar bar = scrollPane.getVerticalScrollBar();
365: int value = Math.min(Math.max(0, bar.getValue()
366: + direction), bar.getMaximum());
367: scrollPane.getVerticalScrollBar().setValue(value);
368: }
369:
370: });
371:
372: /*
373: upPane.addMouseListener( new Mover( -1));
374: upButton.addMouseListener( new Mover( -1));
375: downPane.addMouseListener( new Mover( 1));
376: downButton.addMouseListener( new Mover( 1));
377: */
378: //upPane.setPreferredSize( new Dimension(0,0));
379: //downPane.setPreferredSize( new Dimension(0,0));
380: this .add(upPane, BorderLayout.NORTH);
381: this .add(scrollPane, BorderLayout.CENTER);
382: this .add(downPane, BorderLayout.SOUTH);
383: scrollPane
384: .setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
385: scrollPane
386: .setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
387: scrollPane.getVerticalScrollBar().setEnabled(false);
388: scrollPane.getVerticalScrollBar().setSize(new Dimension(0, 0));
389: scrollPane.getVerticalScrollBar().setPreferredSize(
390: new Dimension(0, 0));
391: scrollPane.getVerticalScrollBar().setMaximumSize(
392: new Dimension(0, 0));
393: scrollPane.getVerticalScrollBar().setMinimumSize(
394: new Dimension(0, 0));
395: m_rowsPerHour = rowsPerHour;
396: m_minutesPerRow = 60 / m_rowsPerHour;
397: //this.setLayout(new BorderLayout());
398: m_list = new JList();
399: scrollPane.setViewportView(m_list);
400: m_list.setBackground(this .getBackground());
401: //JScrollPane scrollPane = new JScrollPane(m_list, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
402: m_list.setVisibleRowCount(8);
403: m_list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
404: m_list.addMouseListener(this );
405: m_list.addMouseMotionListener(this );
406: m_list.setCellRenderer(new DefaultListCellRenderer() {
407: private static final long serialVersionUID = 1L;
408:
409: public Component getListCellRendererComponent(JList list,
410: Object value, int index, boolean isSelected,
411: boolean cellHasFocus) {
412: Component component = super
413: .getListCellRendererComponent(list, value,
414: index, isSelected, cellHasFocus);
415: if (m_renderer != null && !isSelected && !cellHasFocus) {
416: int hour = getHourForIndex(index);
417: int minute = getMinuteForIndex(index);
418: Color color = m_renderer.getBackgroundColor(hour,
419: minute);
420: if (color != null) {
421: component.setBackground(color);
422: }
423: }
424: return component;
425: }
426: });
427: }
428:
429: public void setModel(TimeModel model, DateFormat format) {
430: m_timeModel = model;
431: m_format = (DateFormat) format.clone();
432: Calendar calendar = Calendar.getInstance(
433: m_format.getTimeZone(), model.getLocale());
434: DefaultListModel listModel = new DefaultListModel();
435: for (int i = 0; i < 24 * m_rowsPerHour; i++) {
436: calendar.set(Calendar.HOUR_OF_DAY, i / m_rowsPerHour);
437: calendar.set(Calendar.MINUTE, (i % m_rowsPerHour)
438: * m_minutesPerRow);
439: listModel.addElement(" "
440: + m_format.format(calendar.getTime()) + " ");
441: }
442: m_list.setModel(listModel);
443: int pos = (int) getPreferredSize().getWidth() / 2 - 5;
444: upButton.setLeftPosition(pos);
445: downButton.setLeftPosition(pos);
446: }
447:
448: public void setTimeRenderer(TimeRenderer renderer) {
449: m_renderer = renderer;
450: }
451:
452: public JList getList() {
453: return m_list;
454: }
455:
456: public void setFont(Font font) {
457: super .setFont(font);
458: if (m_list == null || font == null)
459: return;
460: m_list.setFont(font);
461: int pos = (int) getPreferredSize().getWidth() / 2 - 5;
462: upButton.setLeftPosition(pos);
463: downButton.setLeftPosition(pos);
464: }
465:
466: /** Implementation-specific. Should be private.*/
467: public void mousePressed(MouseEvent e) {
468: ok();
469: }
470:
471: /** Implementation-specific. Should be private.*/
472: public void mouseClicked(MouseEvent e) {
473: }
474:
475: /** Implementation-specific. Should be private.*/
476: public void mouseReleased(MouseEvent e) {
477: }
478:
479: /** Implementation-specific. Should be private.*/
480: public void mouseEntered(MouseEvent me) {
481: }
482:
483: /** Implementation-specific. Should be private.*/
484: public void mouseExited(MouseEvent me) {
485: }
486:
487: private int lastIndex = -1;
488: private int lastY = -1;
489:
490: public void mouseDragged(MouseEvent e) {
491: }
492:
493: public void mouseMoved(MouseEvent e) {
494: if (e.getY() == lastY)
495: return;
496: lastY = e.getY();
497: Point p = new Point(e.getX(), e.getY());
498: int index = m_list.locationToIndex(p);
499: if (index == lastIndex)
500: return;
501: lastIndex = index;
502: m_list.setSelectedIndex(index);
503: }
504:
505: public void selectTime(Date time) {
506: Calendar calendar = Calendar.getInstance(m_timeModel
507: .getTimeZone(), m_timeModel.getLocale());
508: calendar.setTime(time);
509: int index = (calendar.get(Calendar.HOUR_OF_DAY))
510: * m_rowsPerHour
511: + (calendar.get(Calendar.MINUTE) / m_minutesPerRow);
512: select(index);
513: }
514:
515: private void select(int index) {
516: m_list.setSelectedIndex(index);
517: m_list.ensureIndexIsVisible(Math.max(index - 3, 0));
518: m_list.ensureIndexIsVisible(Math.min(index + 3, m_list
519: .getModel().getSize() - 1));
520: }
521:
522: // Start of MenuElement implementation
523: public Component getComponent() {
524: return this ;
525: }
526:
527: public MenuElement[] getSubElements() {
528: return new MenuElement[0];
529: }
530:
531: public void menuSelectionChanged(boolean isIncluded) {
532: }
533:
534: public void processKeyEvent(KeyEvent event, MenuElement[] path,
535: MenuSelectionManager manager) {
536: int index;
537: if (event.getID() == KeyEvent.KEY_PRESSED) {
538: switch (event.getKeyCode()) {
539: case (KeyEvent.VK_KP_UP):
540: case (KeyEvent.VK_UP):
541: index = m_list.getSelectedIndex();
542: if (index > 0)
543: select(index - 1);
544: break;
545: case (KeyEvent.VK_KP_DOWN):
546: case (KeyEvent.VK_DOWN):
547: index = m_list.getSelectedIndex();
548: if (index < m_list.getModel().getSize() - 1)
549: select(index + 1);
550: break;
551: case (KeyEvent.VK_SPACE):
552: case (KeyEvent.VK_ENTER):
553: ok();
554: break;
555: case (KeyEvent.VK_ESCAPE):
556: manager.clearSelectedPath();
557: break;
558: }
559: // System.out.println(event.getKeyCode());
560: event.consume();
561: }
562: }
563:
564: private int getHourForIndex(int index) {
565: return index / m_rowsPerHour;
566: }
567:
568: private int getMinuteForIndex(int index) {
569: return (index % m_rowsPerHour) * m_minutesPerRow;
570:
571: }
572:
573: private void ok() {
574: int index = m_list.getSelectedIndex();
575: int hour = getHourForIndex(index);
576: int minute = getMinuteForIndex(index);
577: Calendar calendar = Calendar.getInstance(m_timeModel
578: .getTimeZone(), m_timeModel.getLocale());
579: if (hour >= 0) {
580: calendar.set(Calendar.HOUR_OF_DAY, hour);
581: calendar.set(Calendar.MINUTE, minute);
582: calendar.set(Calendar.SECOND, 0);
583: calendar.set(Calendar.MILLISECOND, 0);
584: m_timeModel.setTime(calendar.getTime());
585: }
586: }
587:
588: public void processMouseEvent(MouseEvent event, MenuElement[] path,
589: MenuSelectionManager manager) {
590: }
591: // End of MenuElement implementation
592: }
|