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