/plugins/SVNPlugin/tags/1.0.0/src/ise/plugin/svn/gui/dateselector/DateSelectorPanel.java

# · Java · 466 lines · 214 code · 70 blank · 182 comment · 22 complexity · 039b6dc5a0759e9505fd34298dda4283 MD5 · raw file

  1. package ise.plugin.svn.gui.dateselector;
  2. import javax.swing.*;
  3. import javax.swing.border.*;
  4. import java.awt.event.*;
  5. import java.awt.*;
  6. import java.util.Date;
  7. import java.util.Calendar;
  8. import java.net.URL;
  9. /** A calendar-dispaly/date-selection widget.
  10. * Here's what it looks like:
  11. * <blockquote>
  12. * <img src="../../../images/DateSelector.gif">
  13. * </blockquote>
  14. * "Today" is highlighted.
  15. * Select a date by clicking on it.
  16. * The background is transparant by default &mdash; it's grey here because
  17. * the underlying window is grey.
  18. * <p>
  19. <img src="../../../images/NavigableDateSelector.gif">
  20. * This "raw" date selector can be "decorated" in several
  21. * ways to make it more useful.
  22. * First, you can add a navigation bar to the bottom
  23. * to advances the
  24. * calandar by one month (single arrow) or one year (double arrow)
  25. * forwards (right-pointing arrow) or backwards (left-pointing arrow).
  26. * "Today" is highlighted.
  27. * Navigation bars are specified using a Gang-of-Four "Decorator"
  28. * object that wraps the raw <code>DateSelectorPanel</code>
  29. * Both the wrapper and the underlying panel implement the
  30. * <code>DateSelectory</code> interface, so can be use
  31. * used interchangably. The following code creates the
  32. * date selector at right.
  33. * <pre>
  34. * DateSelector selector = new DateSelectorPanel();
  35. * selector = new NavigableDateSelector( selector );
  36. * </pre>
  37. * The same thing can be accomplished with a convenience constuctor that
  38. * creates the wrapped DateSelectorPanel for you:
  39. * <pre>
  40. * DateSelector selector = new NavigableDateSelector();
  41. * </pre>
  42. * <p>
  43. * <img src="../../../images/Titled_navigable_date_selector.gif">
  44. * The other augmentation of interest is a title that shows the
  45. * month name and year that's displayed. (there's an example at right).
  46. * Use the same decoration strategy as before to add the title:
  47. * <pre>
  48. * DateSelector selector = new DateSelectorPanel();
  49. * selector = new NavigableDateSelector( selector );
  50. * selector = new TitledDateSelector ( selector );
  51. * </pre>
  52. * You can leave out the navigation bar by ommiting the
  53. * second line of the foregoing code.
  54. * Again, a convenience constructor is provided to create a
  55. * titled date selector (without the navigation bar) as follows:
  56. * <pre>
  57. * DateSelector selector = new TitledDateSelector();
  58. * </pre>
  59. * <p>
  60. * <img src="../../../images/DateSelectorDialog.gif">
  61. * The final variant is the lightweight popup dialog shown at right.
  62. * It can be dragged around by the title bar (though dragging can
  63. * be disabled) and closed by clicking on the "close" icon on the
  64. * upper right. As before, use a decorator to manufacture a dialog:
  65. * <pre>
  66. * DateSelector selector = new DateSelectorPanel();
  67. * selector = new NavigableDateSelector( selector ); // add navigation
  68. * selector = new DateSelectorDialog ( selector );
  69. * </pre>
  70. * Note that you don't need a title because one is supplied for you
  71. * in the dialog-box title bar. Also as before, a convenience
  72. * constructor to create a navigable dialog box like the one at
  73. * right:
  74. * <pre>
  75. * DateSelector = new Date_selectcor_dialog();
  76. * <pre>
  77. * All the earlier examples create a claendar for the current
  78. * month. Several methods are provided, below, to change the date
  79. * in your program. For the most part, they work like simliar
  80. * methods of the {@link Calendar} class.
  81. * <DL>
  82. * <DT><b>Known Problems</b>
  83. * <DD>
  84. * The month names are hard coded (in English). Future versions
  85. * will load these strings from a resource bundle. The week layout
  86. * (S M T W Th F Sa Su) is the default layout for the underlying
  87. * {@link Calendar}, which should change with Locale as appropriate.
  88. * This feature has not been tested, however.
  89. * </DD>
  90. * </DL>
  91. *
  92. * @see ise.plugin.svn.gui.dateselector.DateSelector
  93. * @see ise.plugin.svn.gui.dateselector.DateSelectorDialog
  94. * @see ise.plugin.svn.gui.dateselector.NavigableDateSelector
  95. * @see ise.plugin.svn.gui.dateselector.TitledDateSelector
  96. */
  97. public class DateSelectorPanel extends JPanel implements DateSelector {
  98. //TODO: These strings should be in a resource bundle so they
  99. // can be internationalized.
  100. //
  101. private String[] months =
  102. { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
  103. "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
  104. };
  105. private static final int DAYS_IN_WEEK = 7; // days in a week
  106. private static final int MAX_WEEKS = 6; // maximum weeks in any month
  107. private Date selected = null;
  108. private Calendar calendar = Calendar.getInstance();
  109. {
  110. calendar.set( Calendar.HOUR, 0 );
  111. calendar.set( Calendar.MINUTE, 0 );
  112. calendar.set( Calendar.SECOND, 0 );
  113. }
  114. // The calendar that's displayed on the screen
  115. private final Calendar today = Calendar.getInstance();
  116. // An ActionListener that fields all events coming in from the
  117. // calendar
  118. //
  119. private final Button_handler day_listener = new Button_handler();
  120. // "days" is not a two-dimensional array. I drop buttons into
  121. // a gridLayout and let the layout manager worry about
  122. // what goes where. The first buttion is the first day of the
  123. // first week on the grid, the 8th button is the first day of the
  124. // second week of the grid, and so forth.
  125. private JButton[] days = new JButton[ DAYS_IN_WEEK * MAX_WEEKS ];
  126. {
  127. for ( int i = 0; i < days.length; i++ ) {
  128. JButton day = new JButton( "--" );
  129. days[ i ] = day;
  130. day.setBorder ( new EmptyBorder( 1, 2, 1, 2 ) );
  131. day.setFocusPainted ( false );
  132. day.setActionCommand ( "D" );
  133. day.addActionListener ( day_listener );
  134. day.setOpaque ( false );
  135. }
  136. }
  137. /** Create a DateSelector representing the current date.
  138. */
  139. public DateSelectorPanel() {
  140. JPanel calendar_display = new JPanel();
  141. calendar_display.setOpaque( false );
  142. calendar_display.setBorder( BorderFactory.createEmptyBorder( 5, 3, 0, 1 ) );
  143. calendar_display.setLayout( new GridLayout( MAX_WEEKS /*rows*/, DAYS_IN_WEEK /*columns*/ ) );
  144. for ( int i = 0; i < days.length; ++i )
  145. calendar_display.add( days[ i ] );
  146. setOpaque( false );
  147. setLayout( new BorderLayout() );
  148. add( calendar_display, BorderLayout.CENTER );
  149. updateCalendarDisplay();
  150. }
  151. /** Create a DateSelectorPanel for an arbitrary date.
  152. * @param initial_date Calendar will display this date. The specified
  153. * date is highlighted as "today".
  154. * @see #DateSelectorPanel(int,int,int)
  155. */
  156. public DateSelectorPanel( Date initial_date ) {
  157. this();
  158. calendar.setTime( initial_date );
  159. today. setTime( initial_date );
  160. updateCalendarDisplay();
  161. }
  162. /** Create a DateSelectorPanel for an arbitrary date.
  163. * @param year the full year (e.g. 2003)
  164. * @param month the month id (0=january, 1=feb, etc. [this is the
  165. * convention supported by the other date classes])
  166. * @param day the day of the month. This day will be highlighted
  167. * as "today" on the displayed calendar. Use 0 to suppress
  168. * the highlighting.
  169. * @see #DateSelectorPanel(Date)
  170. */
  171. public DateSelectorPanel( int year, int month, int day ) {
  172. this();
  173. calendar.set( year, month, day );
  174. if ( day != 0 )
  175. today.set( year, month, day );
  176. updateCalendarDisplay();
  177. }
  178. /************************************************************************
  179. * List of observers.
  180. */
  181. private ActionListener subscribers = null;
  182. /** Add a listener that's notified when the user scrolls the
  183. * selector or picks a date.
  184. * @see DateSelector
  185. */
  186. public synchronized void addActionListener( ActionListener l ) {
  187. subscribers = AWTEventMulticaster.add( subscribers, l );
  188. }
  189. /** Remove a listener.
  190. * @see DateSelector
  191. */
  192. public synchronized void removeActionListener( ActionListener l ) {
  193. subscribers = AWTEventMulticaster.remove( subscribers, l );
  194. }
  195. /** Notify the listeners of a scroll or select
  196. */
  197. private void fireActionEvent( int id, String command ) {
  198. if ( subscribers != null )
  199. subscribers.actionPerformed( new ActionEvent( this, id, command ) );
  200. }
  201. /***********************************************************************
  202. * Handle clicks from the buttons that represent calendar days.
  203. */
  204. private class Button_handler implements ActionListener {
  205. public void actionPerformed( ActionEvent e ) {
  206. if ( e.getActionCommand().equals( "D" ) ) {
  207. String text = ( ( JButton ) e.getSource() ).getText();
  208. if ( text.length() > 0 ) // <=0 means click on blank square. Ignore.
  209. { calendar.set
  210. ( calendar.get( Calendar.YEAR ), // Reset the calendar
  211. calendar.get( Calendar.MONTH ), // to be the choosen
  212. Integer.parseInt( text ) // date.
  213. );
  214. selected = calendar.getTime();
  215. fireActionEvent( SELECT_ACTION, selected.toString() );
  216. }
  217. }
  218. }
  219. }
  220. //----------------------------------------------------------------------
  221. private JButton highlighted = null;
  222. private void clearHighlight() {
  223. if ( highlighted != null ) {
  224. highlighted.setBackground( Color.WHITE );
  225. highlighted.setForeground( Color.BLACK );
  226. highlighted.setOpaque( false );
  227. highlighted = null;
  228. }
  229. }
  230. private void highlight( JButton cell ) {
  231. highlighted = cell;
  232. cell.setBackground( ise.plugin.svn.gui.dateselector.Colors.DARK_RED );
  233. cell.setForeground( Color.WHITE );
  234. cell.setOpaque( true );
  235. }
  236. //----------------------------------------------------------------------
  237. /** Redraw the buttons that comprise the calandar to display the current month */
  238. private void updateCalendarDisplay() {
  239. try {
  240. setVisible( false ); // improves paint speed & reduces flicker
  241. clearHighlight();
  242. // The buttons that comprise the calendar are in a single
  243. // dimentioned array that was added to a 6x7 grid layout in
  244. // order. Because of the linear structure, it's easy to
  245. // lay out the calendar just by changing the labels on
  246. // the buttons. Here's the algorithm used below
  247. //
  248. // 1) find out the offset to the first day of the month.
  249. // 2) clear everything up to that offset
  250. // 3) add the days of the month
  251. // 4) clear everything else
  252. int month = calendar.get( Calendar.MONTH );
  253. int year = calendar.get( Calendar.YEAR );
  254. fireActionEvent( CHANGE_ACTION, months[ month ] + " " + year );
  255. calendar.set( year, month, 1 ); // first day of the current month.
  256. int first_day_offset = calendar.get( Calendar.DAY_OF_WEEK ); /* 1 */
  257. assert Calendar.SUNDAY == 0;
  258. assert first_day_offset < days.length;
  259. int i = 0;
  260. while ( i < first_day_offset - 1 ) /* 2 */
  261. days[ i++ ].setText( "" );
  262. int day_of_month = 1;
  263. for ( ; i < days.length; ++i ) /* 3 */
  264. {
  265. // Can't get calendar.equals(today) to work, so do it manually
  266. if ( calendar.get( Calendar.MONTH ) == today.get( Calendar.MONTH )
  267. && calendar.get( Calendar.YEAR ) == today.get( Calendar.YEAR )
  268. && calendar.get( Calendar.DATE ) == today.get( Calendar.DATE ) ) {
  269. highlight( days[ i ] );
  270. }
  271. days[ i ].setText( String.valueOf( day_of_month ) );
  272. calendar.roll( Calendar.DATE, /*up=*/ true ); // forward one day
  273. day_of_month = calendar.get( Calendar.DATE );
  274. if ( day_of_month == 1 )
  275. break;
  276. }
  277. // Note that we break out of the previous loop with i positioned
  278. // at the last day we added, thus the following ++ *must* be a
  279. // preincrement becasue we want to start clearing at the cell
  280. // after that.
  281. while ( ++i < days.length ) /* 4 */
  282. days[ i ].setText( "" );
  283. setVisible( true );
  284. }
  285. catch(Exception e) {
  286. e.printStackTrace();
  287. }
  288. }
  289. /** Create a naviagion button with an image appropriate to the caption.
  290. * The <code>caption</code> argument is used as the button's "action command."
  291. * This method is public only because it has to be. (It overrides a public
  292. * method.) Pretend it's not here.
  293. */
  294. public void addNotify() {
  295. super.addNotify();
  296. int month = calendar.get( Calendar.MONTH );
  297. int year = calendar.get( Calendar.YEAR );
  298. fireActionEvent( CHANGE_ACTION, months[ month ] + " " + year );
  299. }
  300. /** Returns the {@link Date Date} selected by the user or null if
  301. * the window was closed without selecting a date. The returned
  302. * Date has hours, minutes, and seconds values of 0.
  303. */
  304. public Date getSelectedDate() {
  305. return selected;
  306. }
  307. /** Returns the currently displayed {@link Date Date}. */
  308. public Date getCurrentDate() {
  309. return calendar.getTime();
  310. }
  311. /** Works just like {@link Calendar#roll(int,boolean)}. */
  312. public void roll( int field, boolean up ) {
  313. calendar.roll( field, up );
  314. updateCalendarDisplay();
  315. }
  316. /** Works just like {@link Calendar#roll(int,int)}. */
  317. public void roll( int field, int amount ) {
  318. calendar.roll( field, amount );
  319. updateCalendarDisplay();
  320. }
  321. /** Works just like {@link Calendar#set(int,int,int)}
  322. * Sets "today" (which is higlighted) to the indicated day.
  323. */
  324. public void set( int year, int month, int date ) {
  325. calendar.set( year, month, date );
  326. today.set( year, month, date );
  327. updateCalendarDisplay();
  328. }
  329. /** Works just like {@link Calendar#get(int)} */
  330. public int get( int field ) {
  331. return calendar.get( field );
  332. }
  333. /** Works just like {@link Calendar#setTime(Date)},
  334. * Sets "today" (which is higlighted) to the indicated day.
  335. */
  336. public void setTime( Date d ) {
  337. calendar.setTime( d );
  338. today.setTime( d );
  339. updateCalendarDisplay();
  340. }
  341. /** Works just like {@link Calendar#getTime} */
  342. public Date getTime( ) {
  343. return calendar.getTime();
  344. }
  345. /** Return a Calendar object that represents the currently-displayed
  346. * month and year. Modifying this object will not affect the
  347. * current panel.
  348. * @return a Calendar representing the panel's state.
  349. */
  350. public Calendar getCalendar() {
  351. Calendar c = Calendar.getInstance();
  352. c.setTime( calendar.getTime() );
  353. return c;
  354. }
  355. /** Change the display to match the indicated calendar. This Calendar
  356. * argument is used only to provide the new date/time information.
  357. * Modifying it after a call to the current method will not affect
  358. * the DateSelectorPanel at all.
  359. * Sets "today" (which is higlighted) to the indicated day.
  360. * @param calendar A calendar positioned t the date to display.
  361. */
  362. public void setFromCalendar( Calendar calendar ) {
  363. this.calendar.setTime( calendar.getTime() );
  364. today.setTime( calendar.getTime() );
  365. updateCalendarDisplay();
  366. }
  367. //----------------------------------------------------------------------
  368. private static class Test {
  369. public static void main( String[] args ) {
  370. JFrame frame = new JFrame();
  371. frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
  372. frame.getContentPane().setLayout( new FlowLayout() );
  373. DateSelector left = new TitledDateSelector( new NavigableDateSelector() );
  374. DateSelector center = new NavigableDateSelector();
  375. DateSelector right = new DateSelectorPanel( 1900, 1, 2 );
  376. ( ( NavigableDateSelector ) center ).changeNavigationBarColor( null ); // transparent
  377. ActionListener l =
  378. new ActionListener() {
  379. public void actionPerformed( ActionEvent e ) {
  380. System.out.println( e.getActionCommand() );
  381. }
  382. };
  383. left.addActionListener ( l );
  384. center.addActionListener( l );
  385. right.addActionListener ( l );
  386. JPanel white = new JPanel(); // proove that it's transparent.
  387. white.setBackground( Color.WHITE );
  388. white.add( ( JPanel ) center );
  389. frame.getContentPane().add( ( JPanel ) left ); // I hate these casts, but they're
  390. frame.getContentPane().add( white ); // mandated by the fact that
  391. frame.getContentPane().add( ( JPanel ) right ); // Component is not an interface.
  392. frame.pack();
  393. frame.setVisible( true );
  394. }
  395. }
  396. }