001: package net.sourceforge.squirrel_sql.fw.datasetviewer.cellcomponent;
002:
003: /*
004: * Copyright (C) 2001-2004 Colin Bell
005: * colbell@users.sourceforge.net
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2.1 of the License, or (at your option) any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this library; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020: */
021: import java.awt.GridBagConstraints;
022: import java.awt.GridBagLayout;
023: import java.awt.Insets;
024: import java.awt.event.KeyEvent;
025: import java.awt.event.MouseAdapter;
026: import java.awt.event.MouseEvent;
027: import java.io.FileInputStream;
028: import java.io.FileOutputStream;
029: import java.io.IOException;
030: import java.io.InputStreamReader;
031: import java.io.OutputStreamWriter;
032: import java.sql.Date;
033: import java.sql.PreparedStatement;
034: import java.sql.ResultSet;
035: import java.text.DateFormat;
036:
037: import javax.swing.BorderFactory;
038: import javax.swing.JCheckBox;
039: import javax.swing.JComboBox;
040: import javax.swing.JTable;
041: import javax.swing.JTextArea;
042: import javax.swing.JTextField;
043: import javax.swing.SwingUtilities;
044: import javax.swing.event.ChangeEvent;
045: import javax.swing.event.ChangeListener;
046: import javax.swing.text.JTextComponent;
047:
048: import net.sourceforge.squirrel_sql.fw.datasetviewer.CellDataPopup;
049: import net.sourceforge.squirrel_sql.fw.datasetviewer.ColumnDisplayDefinition;
050: import net.sourceforge.squirrel_sql.fw.dialects.DialectFactory;
051: import net.sourceforge.squirrel_sql.fw.gui.OkJPanel;
052: import net.sourceforge.squirrel_sql.fw.gui.RightLabel;
053: import net.sourceforge.squirrel_sql.fw.sql.ISQLDatabaseMetaData;
054: import net.sourceforge.squirrel_sql.fw.util.StringManager;
055: import net.sourceforge.squirrel_sql.fw.util.StringManagerFactory;
056: import net.sourceforge.squirrel_sql.fw.util.ThreadSafeDateFormat;
057: import net.sourceforge.squirrel_sql.fw.util.log.ILogger;
058: import net.sourceforge.squirrel_sql.fw.util.log.LoggerController;
059:
060: /**
061: * @author gwg
062: *
063: * This class provides the display components for handling Date data types,
064: * specifically SQL type DATE.
065: * The display components are for:
066: * <UL>
067: * <LI> read-only display within a table cell
068: * <LI> editing within a table cell
069: * <LI> read-only or editing display within a separate window
070: * </UL>
071: * The class also contains
072: * <UL>
073: * <LI> a function to compare two display values
074: * to see if they are equal. This is needed because the display format
075: * may not be the same as the internal format, and all internal object
076: * types may not provide an appropriate equals() function.
077: * <LI> a function to return a printable text form of the cell contents,
078: * which is used in the text version of the table.
079: * </UL>
080: * <P>
081: * The components returned from this class extend RestorableJTextField
082: * and RestorableJTextArea for use in editing table cells that
083: * contain values of this data type. It provides the special behavior for null
084: * handling and resetting the cell to the original value.
085: */
086:
087: public class DataTypeDate extends BaseDataTypeComponent implements
088: IDataTypeComponent {
089: private static final StringManager s_stringMgr = StringManagerFactory
090: .getStringManager(DataTypeDate.class);
091:
092: /** Logger for this class. */
093: private static ILogger s_log = LoggerController
094: .createLogger(DataTypeDate.class);
095:
096: /* whether nulls are allowed or not */
097: private boolean _isNullable;
098:
099: /* table of which we are part (needed for creating popup dialog) */
100: private JTable _table;
101:
102: /* The JTextComponent that is being used for editing */
103: private IRestorableTextComponent _textComponent;
104:
105: /* The CellRenderer used for this data type */
106: //??? For now, use the same renderer as everyone else.
107: //??
108: //?? IN FUTURE: change this to use a new instance of renederer
109: //?? for this data type.
110: private DefaultColumnRenderer _renderer = DefaultColumnRenderer
111: .getInstance();
112:
113: /**
114: * Name of this class, which is needed because the class name is needed
115: * by the static method getControlPanel, so we cannot use something
116: * like getClass() to find this name.
117: */
118: private static final String this ClassName = "net.sourceforge.squirrel_sql.fw.datasetviewer.cellcomponent.DataTypeDate";
119:
120: /** Default date format */
121: private static int DEFAULT_LOCALE_FORMAT = DateFormat.SHORT;
122:
123: /*
124: * Properties settable by the user
125: */
126: // flag for whether we have already loaded the properties or not
127: private static boolean propertiesAlreadyLoaded = false;
128:
129: // flag for whether to use the default Java format (true)
130: // or the Locale-dependent format (false)
131: private static boolean useJavaDefaultFormat = true;
132:
133: // which locale-dependent format to use; short, medium, long, or full
134: private static int localeFormat = DEFAULT_LOCALE_FORMAT;
135:
136: // Whether to force user to enter dates in exact format or use heuristics to guess it
137: private static boolean lenient = true;
138:
139: // Whether or not to read date type columns with rs.getTimestamp()
140: private static boolean readDateAsTimestamp = false;
141:
142: // The DateFormat object to use for all locale-dependent formatting.
143: // This is reset each time the user changes the previous settings.
144: private static ThreadSafeDateFormat dateFormat = new ThreadSafeDateFormat(
145: localeFormat);
146: private boolean _renderExceptionHasBeenLogged;
147:
148: /**
149: * Constructor - save the data needed by this data type.
150: */
151: public DataTypeDate(JTable table, ColumnDisplayDefinition colDef) {
152: _table = table;
153: _colDef = colDef;
154: _isNullable = colDef.isNullable();
155:
156: loadProperties();
157: }
158:
159: /** Internal function to get the user-settable properties from the DTProperties,
160: * if they exist, and to ensure that defaults are set if the properties have
161: * not yet been created.
162: * <P>
163: * This method may be called from different places depending on whether
164: * an instance of this class is created before the user brings up the Session
165: * Properties window. In either case, the data is static and is set only
166: * the first time we are called.
167: */
168: private static void loadProperties() {
169:
170: //set the property values
171: // Note: this may have already been done by another instance of
172: // this DataType created to handle a different column.
173: if (propertiesAlreadyLoaded == false) {
174: propertiesAlreadyLoaded = true;
175: // get parameters previously set by user, or set default values
176: useJavaDefaultFormat = true; // set to use the Java default
177: String useJavaDefaultFormatString = DTProperties.get(
178: this ClassName, "useJavaDefaultFormat");
179: if (useJavaDefaultFormatString != null
180: && useJavaDefaultFormatString.equals("false"))
181: useJavaDefaultFormat = false;
182:
183: // get which locale-dependent format to use
184: localeFormat = DateFormat.SHORT; // set to use the Java default
185: String localeFormatString = DTProperties.get(this ClassName,
186: "localeFormat");
187: if (localeFormatString != null)
188: localeFormat = Integer.parseInt(localeFormatString);
189:
190: // use lenient input or force user to enter exact format
191: lenient = true; // set to allow less stringent input
192: String lenientString = DTProperties.get(this ClassName,
193: "lenient");
194: if (lenientString != null && lenientString.equals("false"))
195: lenient = false;
196:
197: // Bug #1757076
198: // always use false unless user specifies otherwise; this breaks
199: // date editing in Derby (possibly DB2 as well)
200: readDateAsTimestamp = false;
201: String readDateAsTimestampString = DTProperties.get(
202: this ClassName, "readDateAsTimestamp");
203: if (readDateAsTimestampString != null
204: && readDateAsTimestampString.equals("true")) {
205: readDateAsTimestamp = true;
206: }
207: }
208: }
209:
210: public static boolean getReadDateAsTimestamp() {
211: propertiesAlreadyLoaded = false;
212: loadProperties();
213: return readDateAsTimestamp;
214: }
215:
216: /**
217: * Return the name of the java class used to hold this data type.
218: */
219: public String getClassName() {
220: return "java.sql.Date";
221: }
222:
223: /**
224: * Determine if two objects of this data type contain the same value.
225: * Neither of the objects is null
226: */
227: public boolean areEqual(Object obj1, Object obj2) {
228: return ((Date) obj1).equals(obj2);
229: }
230:
231: /*
232: * First we have the methods for in-cell and Text-table operations
233: */
234:
235: /**
236: * Render a value into text for this DataType.
237: */
238: public String renderObject(Object value) {
239: // use the Java default date-to-string
240: if (useJavaDefaultFormat == true || value == null)
241: return (String) _renderer.renderObject(value);
242:
243: // use a date formatter
244: try {
245: return (String) _renderer.renderObject(dateFormat
246: .format(value));
247: } catch (Exception e) {
248: if (false == _renderExceptionHasBeenLogged) {
249: _renderExceptionHasBeenLogged = true;
250: s_log.error("Could not format \"" + value
251: + "\" as date type", e);
252: }
253: return (String) _renderer.renderObject(value);
254: }
255: }
256:
257: /**
258: * This Data Type can be edited in a table cell.
259: */
260: public boolean isEditableInCell(Object originalValue) {
261: return true;
262: }
263:
264: /**
265: * See if a value in a column has been limited in some way and
266: * needs to be re-read before being used for editing.
267: * For read-only tables this may actually return true since we want
268: * to be able to view the entire contents of the cell even if it was not
269: * completely loaded during the initial table setup.
270: */
271: public boolean needToReRead(Object originalValue) {
272: // this DataType does not limit the data read during the initial load of the table,
273: // so there is no need to re-read the complete data later
274: return false;
275: }
276:
277: /**
278: * Return a JTextField usable in a CellEditor.
279: */
280: public JTextField getJTextField() {
281: _textComponent = new RestorableJTextField();
282:
283: // special handling of operations while editing this data type
284: ((RestorableJTextField) _textComponent)
285: .addKeyListener(new KeyTextHandler());
286:
287: //
288: // handle mouse events for double-click creation of popup dialog.
289: // This happens only in the JTextField, not the JTextArea, so we can
290: // make this an inner class within this method rather than a separate
291: // inner class as is done with the KeyTextHandler class.
292: //
293: ((RestorableJTextField) _textComponent)
294: .addMouseListener(new MouseAdapter() {
295: public void mousePressed(MouseEvent evt) {
296: if (evt.getClickCount() == 2) {
297: MouseEvent tableEvt = SwingUtilities
298: .convertMouseEvent(
299: (RestorableJTextField) DataTypeDate.this ._textComponent,
300: evt,
301: DataTypeDate.this ._table);
302: CellDataPopup.showDialog(
303: DataTypeDate.this ._table,
304: DataTypeDate.this ._colDef,
305: tableEvt, true);
306: }
307: }
308: }); // end of mouse listener
309:
310: return (JTextField) _textComponent;
311: }
312:
313: /**
314: * Implement the interface for validating and converting to internal object.
315: * Null is a valid successful return, so errors are indicated only by
316: * existance or not of a message in the messageBuffer.
317: */
318: public Object validateAndConvert(String value,
319: Object originalValue, StringBuffer messageBuffer) {
320: // handle null, which is shown as the special string "<null>"
321: if (value.equals("<null>") || value.equals(""))
322: return null;
323:
324: // Do the conversion into the object in a safe manner
325: try {
326: if (useJavaDefaultFormat) {
327: Object obj = Date.valueOf(value);
328: return obj;
329: } else {
330: // use the DateFormat to parse
331: java.util.Date javaDate = dateFormat.parse(value);
332: java.sql.Date sqlDate = new java.sql.Date(javaDate
333: .getTime());
334: return sqlDate;
335: }
336: } catch (Exception e) {
337: messageBuffer.append(e.toString() + "\n");
338: //?? do we need the message also, or is it automatically part of the toString()?
339: //messageBuffer.append(e.getMessage());
340: return null;
341: }
342: }
343:
344: /**
345: * If true, this tells the PopupEditableIOPanel to use the
346: * binary editing panel rather than a pure text panel.
347: * The binary editing panel assumes the data is an array of bytes,
348: * converts it into text form, allows the user to change how that
349: * data is displayed (e.g. Hex, Decimal, etc.), and converts
350: * the data back from text to bytes when the user editing is completed.
351: * If this returns false, this DataType class must
352: * convert the internal data into a text string that
353: * can be displayed (and edited, if allowed) in a TextField
354: * or TextArea, and must handle all
355: * user key strokes related to editing of that data.
356: */
357: public boolean useBinaryEditingPanel() {
358: return false;
359: }
360:
361: /*
362: * Now the functions for the Popup-related operations.
363: */
364:
365: /**
366: * Returns true if data type may be edited in the popup,
367: * false if not.
368: */
369: public boolean isEditableInPopup(Object originalValue) {
370: return true;
371: }
372:
373: /*
374: * Return a JTextArea usable in the CellPopupDialog
375: * and fill in the value.
376: */
377: public JTextArea getJTextArea(Object value) {
378: _textComponent = new RestorableJTextArea();
379:
380: // value is a simple string representation of the data,
381: // the same one used in Text and in-cell operations.
382: ((RestorableJTextArea) _textComponent)
383: .setText(renderObject(value));
384:
385: // special handling of operations while editing this data type
386: ((RestorableJTextArea) _textComponent)
387: .addKeyListener(new KeyTextHandler());
388:
389: return (RestorableJTextArea) _textComponent;
390: }
391:
392: /**
393: * Validating and converting in Popup is identical to cell-related operation.
394: */
395: public Object validateAndConvertInPopup(String value,
396: Object originalValue, StringBuffer messageBuffer) {
397: return validateAndConvert(value, originalValue, messageBuffer);
398: }
399:
400: /*
401: * The following is used in both cell and popup operations.
402: */
403:
404: /*
405: * Internal class for handling key events during editing
406: * of both JTextField and JTextArea.
407: */
408: private class KeyTextHandler extends BaseKeyTextHandler {
409: public void keyTyped(KeyEvent e) {
410: char c = e.getKeyChar();
411:
412: // as a coding convenience, create a reference to the text component
413: // that is typecast to JTextComponent. this is not essential, as we
414: // could typecast every reference, but this makes the code cleaner
415: JTextComponent _theComponent = (JTextComponent) DataTypeDate.this ._textComponent;
416: String text = _theComponent.getText();
417:
418: // tabs and newlines get put into the text before this check,
419: // so remove them
420: // This only applies to Popup editing since these chars are
421: // not passed to this level by the in-cell editor.
422: if (c == KeyEvent.VK_TAB || c == KeyEvent.VK_ENTER) {
423: // remove all instances of the offending char
424: int index = text.indexOf(c);
425: if (index != -1) {
426: if (index == text.length() - 1) {
427: text = text.substring(0, text.length() - 1); // truncate string
428: } else {
429: text = text.substring(0, index)
430: + text.substring(index + 1);
431: }
432: ((IRestorableTextComponent) _theComponent)
433: .updateText(text);
434: _theComponent.getToolkit().beep();
435: }
436: e.consume();
437: }
438:
439: // handle cases of null
440: // The processing is different when nulls are allowed and when they are not.
441: //
442:
443: if (DataTypeDate.this ._isNullable) {
444:
445: // user enters something when field is null
446: if (text.equals("<null>")) {
447: if ((c == KeyEvent.VK_BACK_SPACE)
448: || (c == KeyEvent.VK_DELETE)) {
449: // delete when null => original value
450: DataTypeDate.this ._textComponent.restoreText();
451: e.consume();
452: } else {
453: // non-delete when null => clear field and add text
454: DataTypeDate.this ._textComponent.updateText("");
455: // fall through to normal processing of this key stroke
456: }
457: } else {
458: // check for user deletes last thing in field
459: if ((c == KeyEvent.VK_BACK_SPACE)
460: || (c == KeyEvent.VK_DELETE)) {
461: if (text.length() <= 1) {
462: // about to delete last thing in field, so replace with null
463: DataTypeDate.this ._textComponent
464: .updateText("<null>");
465: e.consume();
466: }
467: }
468: }
469: } else {
470: // field is not nullable
471: //
472: handleNotNullableField(text, c, e, _textComponent);
473: }
474: }
475: }
476:
477: /*
478: * DataBase-related functions
479: */
480:
481: /**
482: * On input from the DB, read the data from the ResultSet into the appropriate
483: * type of object to be stored in the table cell.
484: */
485: public Object readResultSet(ResultSet rs, int index,
486: boolean limitDataRead) throws java.sql.SQLException {
487:
488: return staticReadResultSet(rs, index, limitDataRead);
489: }
490:
491: /**
492: * On input from the DB, read the data from the ResultSet into the appropriate
493: * type of object to be stored in the table cell.
494: */
495: @SuppressWarnings("unused")
496: public static Object staticReadResultSet(ResultSet rs, int index,
497: boolean limitDataRead) throws java.sql.SQLException {
498: loadProperties();
499: Object data = null;
500:
501: data = rs.getDate(index);
502:
503: if (rs.wasNull()) {
504: return null;
505: } else {
506: return data;
507: }
508: }
509:
510: /**
511: * When updating the database, generate a string form of this object value
512: * that can be used in the WHERE clause to match the value in the database.
513: * A return value of null means that this column cannot be used in the WHERE
514: * clause, while a return of "null" (or "is null", etc) means that the column
515: * can be used in the WHERE clause and the value is actually a null value.
516: * This function must also include the column label so that its output
517: * is of the form:
518: * "columnName = value"
519: * or
520: * "columnName is null"
521: * or whatever is appropriate for this column in the database.
522: */
523: public String getWhereClauseValue(Object value,
524: ISQLDatabaseMetaData md) {
525: if (value == null || value.toString() == null
526: || value.toString().length() == 0) {
527: return _colDef.getLabel() + " IS NULL";
528: } else {
529: // if value contains ":" it probably has a time component
530: boolean hasTimeComponent = (value.toString().indexOf(":") != -1);
531:
532: // if value contains "-" it probably has a date component
533: boolean hasDateComponent = (value.toString().indexOf("-") != -1);
534:
535: if (hasTimeComponent && hasDateComponent) {
536: // treat it like a timestamp
537: return _colDef.getLabel() + "={ts '" + value.toString()
538: + "'}";
539: } else if (hasTimeComponent) {
540: // treat it like a time - no date component
541: return _colDef.getLabel() + "={t '" + value.toString()
542: + "'}";
543: } else {
544: if (DialectFactory.isOracle(md)) {
545: // Oracle stores time information in java.sql.Types.Date columns
546: // This tells Oracle that we are only talking about the date part.
547: return "trunc(" + _colDef.getLabel() + ")={d '"
548: + value.toString() + "'}";
549: } else {
550: return _colDef.getLabel() + "={d '"
551: + value.toString() + "'}";
552: }
553: }
554: }
555: }
556:
557: /**
558: * When updating the database, insert the appropriate datatype into the
559: * prepared statment at the given variable position.
560: */
561: public void setPreparedStatementValue(PreparedStatement pstmt,
562: Object value, int position) throws java.sql.SQLException {
563: if (value == null) {
564: pstmt.setNull(position, _colDef.getSqlType());
565: } else {
566: pstmt.setDate(position, ((Date) value));
567: }
568: }
569:
570: /**
571: * Get a default value for the table used to input data for a new row
572: * to be inserted into the DB.
573: */
574: public Object getDefaultValue(String dbDefaultValue) {
575: if (dbDefaultValue != null) {
576: // try to use the DB default value
577: StringBuffer mbuf = new StringBuffer();
578: Object newObject = validateAndConvert(dbDefaultValue, null,
579: mbuf);
580:
581: // if there was a problem with converting, then just fall through
582: // and continue as if there was no default given in the DB.
583: // Otherwise, use the converted object
584: if (mbuf.length() == 0)
585: return newObject;
586: }
587:
588: // no default in DB. If nullable, use null.
589: if (_isNullable)
590: return null;
591:
592: // field is not nullable, so create a reasonable default value
593: return new Date(new java.util.Date().getTime());
594: }
595:
596: /*
597: * File IO related functions
598: */
599:
600: /**
601: * Say whether or not object can be exported to and imported from
602: * a file. We put both export and import together in one test
603: * on the assumption that all conversions can be done both ways.
604: */
605: public boolean canDoFileIO() {
606: return true;
607: }
608:
609: /**
610: * Read a file and construct a valid object from its contents.
611: * Errors are returned by throwing an IOException containing the
612: * cause of the problem as its message.
613: * <P>
614: * DataType is responsible for validating that the imported
615: * data can be converted to an object, and then must return
616: * a text string that can be used in the Popup window text area.
617: * This object-to-text conversion is the same as is done by
618: * the DataType object internally in the getJTextArea() method.
619: *
620: * <P>
621: * File is assumed to be and ASCII string of digits
622: * representing a value of this data type.
623: */
624: public String importObject(FileInputStream inStream)
625: throws IOException {
626:
627: InputStreamReader inReader = new InputStreamReader(inStream);
628:
629: int fileSize = inStream.available();
630:
631: char charBuf[] = new char[fileSize];
632:
633: int count = inReader.read(charBuf, 0, fileSize);
634:
635: if (count != fileSize)
636: throw new IOException("Could read only " + count
637: + " chars from a total file size of " + fileSize
638: + ". Import failed.");
639:
640: // convert file text into a string
641: // Special case: some systems tack a newline at the end of
642: // the text read. Assume that if last char is a newline that
643: // we want everything else in the line.
644: String fileText;
645: if (charBuf[count - 1] == KeyEvent.VK_ENTER)
646: fileText = new String(charBuf, 0, count - 1);
647: else
648: fileText = new String(charBuf);
649:
650: // test that the string is valid by converting it into an
651: // object of this data type
652: StringBuffer messageBuffer = new StringBuffer();
653: validateAndConvertInPopup(fileText, null, messageBuffer);
654: if (messageBuffer.length() > 0) {
655: // convert number conversion issue into IO issue for consistancy
656: throw new IOException(
657: "Text does not represent data of type "
658: + getClassName() + ". Text was:\n"
659: + fileText);
660: }
661:
662: // return the text from the file since it does
663: // represent a valid data value
664: return fileText;
665: }
666:
667: /**
668: * Construct an appropriate external representation of the object
669: * and write it to a file.
670: * Errors are returned by throwing an IOException containing the
671: * cause of the problem as its message.
672: * <P>
673: * DataType is responsible for validating that the given text
674: * text from a Popup JTextArea can be converted to an object.
675: * This text-to-object conversion is the same as validateAndConvertInPopup,
676: * which may be used internally by the object to do the validation.
677: * <P>
678: * The DataType object must flush and close the output stream before returning.
679: * Typically it will create another object (e.g. an OutputWriter), and
680: * that is the object that must be flushed and closed.
681: *
682: * <P>
683: * File is assumed to be and ASCII string of digits
684: * representing a value of this data type.
685: */
686: public void exportObject(FileOutputStream outStream, String text)
687: throws IOException {
688:
689: OutputStreamWriter outWriter = new OutputStreamWriter(outStream);
690:
691: // check that the text is a valid representation
692: StringBuffer messageBuffer = new StringBuffer();
693: validateAndConvertInPopup(text, null, messageBuffer);
694: if (messageBuffer.length() > 0) {
695: // there was an error in the conversion
696: throw new IOException(new String(messageBuffer));
697: }
698:
699: // just send the text to the output file
700: outWriter.write(text);
701: outWriter.flush();
702: outWriter.close();
703: }
704:
705: /*
706: * Property change control panel
707: */
708:
709: /**
710: * Generate a JPanel containing controls that allow the user
711: * to adjust the properties for this DataType.
712: * All properties are static accross all instances of this DataType.
713: * However, the class may choose to apply the information differentially,
714: * such as keeping a list (also entered by the user) of table/column names
715: * for which certain properties should be used.
716: * <P>
717: * This is called ONLY if there is at least one property entered into the DTProperties
718: * for this class.
719: * <P>
720: * Since this method is called by reflection on the Method object derived from this class,
721: * it does not need to be included in the Interface.
722: * It would be nice to include this in the Interface for consistancy, documentation, etc,
723: * but the Interface does not seem to like static methods.
724: */
725: public static OkJPanel getControlPanel() {
726:
727: /*
728: * If you add this method to one of the standard DataTypes in the
729: * fw/datasetviewer/cellcomponent directory, you must also add the name
730: * of that DataType class to the list in CellComponentFactory, method
731: * getControlPanels, variable named initialClassNameList.
732: * If the class is being registered with the factory using registerDataType,
733: * then you should not include the class name in the list (it will be found
734: * automatically), but if the DataType is part of the case statement in the
735: * factory method getDataTypeObject, then it does need to be explicitly listed
736: * in the getControlPanels method also.
737: */
738:
739: // if this panel is called before any instances of the class have been
740: // created, we need to load the properties from the DTProperties.
741: loadProperties();
742:
743: return new DateOkJPanel();
744: }
745:
746: // Class that displays the various formats available for dates
747: public static class DateFormatTypeCombo extends JComboBox {
748: private static final long serialVersionUID = 1L;
749:
750: public DateFormatTypeCombo() {
751: // i18n[dataTypeDate.full=Full ({0})]
752: addItem(s_stringMgr.getString("dataTypeDate.full",
753: DateFormat.getDateInstance(DateFormat.FULL).format(
754: new java.util.Date())));
755: // i18n[dataTypeDate.long=Long ({0})]
756: addItem(s_stringMgr.getString("dataTypeDate.long",
757: DateFormat.getDateInstance(DateFormat.LONG).format(
758: new java.util.Date())));
759: // i18n[dataTypeDate.medium=Medium ({0})]
760: addItem(s_stringMgr.getString("dataTypeDate.medium",
761: DateFormat.getDateInstance(DateFormat.MEDIUM)
762: .format(new java.util.Date())));
763: // i18n[dataTypeDate.short=Short ({0})]
764: addItem(s_stringMgr.getString("dataTypeDate.short",
765: DateFormat.getDateInstance(DateFormat.SHORT)
766: .format(new java.util.Date())));
767: }
768:
769: public void setSelectedIndex(int option) {
770: if (option == DateFormat.SHORT)
771: super .setSelectedIndex(3);
772: else if (option == DateFormat.MEDIUM)
773: super .setSelectedIndex(2);
774: else if (option == DateFormat.LONG)
775: super .setSelectedIndex(1);
776: else
777: super .setSelectedIndex(0);
778: }
779:
780: public int getValue() {
781: if (getSelectedIndex() == 3)
782: return DateFormat.SHORT;
783: else if (getSelectedIndex() == 2)
784: return DateFormat.MEDIUM;
785: else if (getSelectedIndex() == 1)
786: return DateFormat.LONG;
787: else
788: return DateFormat.FULL;
789: }
790: }
791:
792: /**
793: * Inner class that extends OkJPanel so that we can call the ok()
794: * method to save the data when the user is happy with it.
795: */
796: private static class DateOkJPanel extends OkJPanel {
797: private static final long serialVersionUID = 1L;
798:
799: /*
800: * GUI components - need to be here because they need to be
801: * accessible from the event handlers to alter each other's state.
802: */
803: // check box for whether to use Java Default or a Locale-dependent format
804: private JCheckBox useJavaDefaultFormatChk = new JCheckBox(
805: // i18n[dataTypeDate.useDefaultFormat=Use default format ({0})]
806: s_stringMgr.getString("dataTypeDate.useDefaultFormat",
807: new java.sql.Date(new java.util.Date()
808: .getTime()).toString()));
809:
810: // label for the date format combo, used to enable/disable text
811: // i18n[dataTypeDate.orlocaleIndependent= or locale-dependent format:]
812: private RightLabel dateFormatTypeDropLabel = new RightLabel(
813: s_stringMgr
814: .getString("dataTypeDate.orlocaleIndependent"));
815:
816: // Combo box for read-all/read-part of blob
817: private DateFormatTypeCombo dateFormatTypeDrop = new DateFormatTypeCombo();
818:
819: // checkbox for whether to interpret input leniently or not
820: // i18n[dataTypeDate.allowInexact=allow inexact format on input]
821: private JCheckBox lenientChk = new JCheckBox(s_stringMgr
822: .getString("dataTypeDate.allowInexact"));
823:
824: // whether or not to read date type columns with rs.getTimestamp()
825: // i18n[dataTypeDate.readDateAsTimestamp=Interpret DATE columns as TIMESTAMP]
826: private JCheckBox readdDateAsTimestampChk = new JCheckBox(
827: s_stringMgr
828: .getString("dataTypeDate.readDateAsTimestamp"));
829:
830: public DateOkJPanel() {
831:
832: /* set up the controls */
833: // checkbox for Java default/non-default format
834: useJavaDefaultFormatChk.setSelected(useJavaDefaultFormat);
835: useJavaDefaultFormatChk
836: .addChangeListener(new ChangeListener() {
837: public void stateChanged(ChangeEvent e) {
838: dateFormatTypeDrop
839: .setEnabled(!useJavaDefaultFormatChk
840: .isSelected());
841: dateFormatTypeDropLabel
842: .setEnabled(!useJavaDefaultFormatChk
843: .isSelected());
844: lenientChk
845: .setEnabled(!useJavaDefaultFormatChk
846: .isSelected());
847: }
848: });
849:
850: // Combo box for read-all/read-part of blob
851: dateFormatTypeDrop = new DateFormatTypeCombo();
852: dateFormatTypeDrop.setSelectedIndex(localeFormat);
853:
854: // lenient checkbox
855: lenientChk.setSelected(lenient);
856:
857: // readdDateAsTimestamp checkbox
858: readdDateAsTimestampChk.setSelected(readDateAsTimestamp);
859:
860: // handle cross-connection between fields
861: dateFormatTypeDrop.setEnabled(!useJavaDefaultFormatChk
862: .isSelected());
863: dateFormatTypeDropLabel.setEnabled(!useJavaDefaultFormatChk
864: .isSelected());
865: lenientChk
866: .setEnabled(!useJavaDefaultFormatChk.isSelected());
867:
868: /*
869: * Create the panel and add the GUI items to it
870: */
871:
872: setLayout(new GridBagLayout());
873:
874: // i18n[dataTypeDate.typeDate=Date (SQL type 91)]
875: setBorder(BorderFactory.createTitledBorder(s_stringMgr
876: .getString("dataTypeDate.typeDate")));
877: final GridBagConstraints gbc = new GridBagConstraints();
878: gbc.fill = GridBagConstraints.HORIZONTAL;
879: gbc.insets = new Insets(4, 4, 4, 4);
880: gbc.anchor = GridBagConstraints.WEST;
881:
882: gbc.gridx = 0;
883: gbc.gridy = 0;
884:
885: gbc.gridwidth = GridBagConstraints.REMAINDER;
886: add(useJavaDefaultFormatChk, gbc);
887:
888: gbc.gridwidth = 1;
889: gbc.gridx = 0;
890: ++gbc.gridy;
891: add(dateFormatTypeDropLabel, gbc);
892:
893: ++gbc.gridx;
894: add(dateFormatTypeDrop, gbc);
895:
896: gbc.gridx = 0;
897: ++gbc.gridy;
898: add(lenientChk, gbc);
899:
900: gbc.gridx = 0;
901: ++gbc.gridy;
902: add(readdDateAsTimestampChk, gbc);
903: } // end of constructor for inner class
904:
905: /**
906: * User has clicked OK in the surrounding JPanel,
907: * so save the current state of all variables
908: */
909: public void ok() {
910: // get the values from the controls and set them in the static properties
911: useJavaDefaultFormat = useJavaDefaultFormatChk.isSelected();
912: DTProperties.put(this ClassName, "useJavaDefaultFormat",
913: Boolean.valueOf(useJavaDefaultFormat).toString());
914:
915: localeFormat = dateFormatTypeDrop.getValue();
916: dateFormat = new ThreadSafeDateFormat(localeFormat); // lenient is set next
917: DTProperties.put(this ClassName, "localeFormat", Integer
918: .toString(localeFormat));
919:
920: lenient = lenientChk.isSelected();
921: dateFormat.setLenient(lenient);
922: DTProperties.put(this ClassName, "lenient", Boolean.valueOf(
923: lenient).toString());
924:
925: readDateAsTimestamp = readdDateAsTimestampChk.isSelected();
926: DTProperties.put(this ClassName, "readDateAsTimestamp",
927: Boolean.valueOf(readDateAsTimestamp).toString());
928: }
929:
930: } // end of inner class
931: }
|