001: /*
002: The contents of this file are subject to the Common Public Attribution License
003: Version 1.0 (the "License"); you may not use this file except in compliance with
004: the License. You may obtain a copy of the License at
005: http://www.projity.com/license . The License is based on the Mozilla Public
006: License Version 1.1 but Sections 14 and 15 have been added to cover use of
007: software over a computer network and provide for limited attribution for the
008: Original Developer. In addition, Exhibit A has been modified to be consistent
009: with Exhibit B.
010:
011: Software distributed under the License is distributed on an "AS IS" basis,
012: WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
013: specific language governing rights and limitations under the License. The
014: Original Code is OpenProj. The Original Developer is the Initial Developer and
015: is Projity, Inc. All portions of the code written by Projity are Copyright (c)
016: 2006, 2007. All Rights Reserved. Contributors Projity, Inc.
017:
018: Alternatively, the contents of this file may be used under the terms of the
019: Projity End-User License Agreeement (the Projity License), in which case the
020: provisions of the Projity License are applicable instead of those above. If you
021: wish to allow use of your version of this file only under the terms of the
022: Projity License and not to allow others to use your version of this file under
023: the CPAL, indicate your decision by deleting the provisions above and replace
024: them with the notice and other provisions required by the Projity License. If
025: you do not delete the provisions above, a recipient may use your version of this
026: file under either the CPAL or the Projity License.
027:
028: [NOTE: The text of this license may differ slightly from the text of the notices
029: in Exhibits A and B of the license at http://www.projity.com/license. You should
030: use the latest text at http://www.projity.com/license for your modifications.
031: You may not remove this license text from the source files.]
032:
033: Attribution Information: Attribution Copyright Notice: Copyright © 2006, 2007
034: Projity, Inc. Attribution Phrase (not exceeding 10 words): Powered by OpenProj,
035: an open source solution from Projity. Attribution URL: http://www.projity.com
036: Graphic Image as provided in the Covered Code as file: openproj_logo.png with
037: alternatives listed on http://www.projity.com/logo
038:
039: Display of Attribution Information is required in Larger Works which are defined
040: in the CPAL as a work which combines Covered Code or portions thereof with code
041: not governed by the terms of the CPAL. However, in addition to the other notice
042: obligations, all copies of the Covered Code in Executable and Source Code form
043: distributed must, as a form of attribution of the original author, include on
044: each user interface screen the "OpenProj" logo visible to all users. The
045: OpenProj logo should be located horizontally aligned with the menu bar and left
046: justified on the top left of the screen adjacent to the File menu. The logo
047: must be at least 100 x 25 pixels. When users click on the "OpenProj" logo it
048: must direct them back to http://www.projity.com.
049: */
050: package com.projity.dialog.util;
051:
052: import java.awt.Color;
053: import java.awt.Component;
054: import java.awt.event.KeyEvent;
055: import java.awt.event.KeyListener;
056: import java.awt.event.MouseEvent;
057: import java.awt.event.MouseListener;
058: import java.beans.PropertyChangeEvent;
059: import java.beans.PropertyChangeListener;
060: import java.text.DateFormat;
061: import java.util.Date;
062:
063: import javax.swing.DefaultComboBoxModel;
064: import javax.swing.JCheckBox;
065: import javax.swing.JComboBox;
066: import javax.swing.JComponent;
067: import javax.swing.JFormattedTextField;
068: import javax.swing.JLabel;
069: import javax.swing.JScrollPane;
070: import javax.swing.JSpinner;
071: import javax.swing.JTextArea;
072: import javax.swing.JTextField;
073: import javax.swing.event.ChangeEvent;
074: import javax.swing.event.ChangeListener;
075: import javax.swing.text.AbstractDocument;
076: import javax.swing.text.JTextComponent;
077:
078: import com.projity.datatype.Hyperlink;
079: import com.projity.dialog.FieldDialog;
080: import com.projity.field.Field;
081: import com.projity.field.FieldContext;
082: import com.projity.field.FieldParseException;
083: import com.projity.field.ObjectRef;
084: import com.projity.field.Range;
085: import com.projity.field.StaticSelect;
086: import com.projity.options.CalendarOption;
087: import com.projity.options.EditOption;
088: import com.projity.pm.graphic.spreadsheet.editor.SpinEditor;
089: import com.projity.pm.task.AccessControlPolicy;
090: import com.projity.pm.task.Project;
091: import com.projity.strings.Messages;
092: import com.projity.util.Alert;
093: import com.projity.util.ClassUtils;
094: import com.projity.util.DateTime;
095: import com.projity.util.MathUtils;
096:
097: /**
098: *
099: */
100: public class ComponentFactory {
101: public static final int READ_ONLY = 1;
102: public static final int SOMETIMES_READ_ONLY = 2;
103:
104: private static double MAX_VALUE = 60000000.0; //same as MS Project
105:
106: private static FieldContext context = null;
107:
108: private static JComponent getFieldComponent(JComponent component) {
109: if (component instanceof JScrollPane)
110: component = (JComponent) ((JScrollPane) component)
111: .getViewport().getComponent(0);
112: return component;
113: }
114:
115: static JTextField getSpinnerTextField(JSpinner spinner) {
116: return ((JSpinner.DefaultEditor) spinner.getEditor())
117: .getTextField();
118: }
119:
120: public static Object getValueFromComponent(JComponent component,
121: Field field) {
122: component = getFieldComponent(component);
123:
124: if (component instanceof JTextField)
125: return ((JTextField) component).getText();
126: else if (component instanceof JTextArea)
127: return ((JTextArea) component).getText();
128: else if (component instanceof JCheckBox)
129: return new Boolean(((JCheckBox) component).isSelected());
130: else if (component instanceof ExtDateField) {
131: return ((ExtDateField) component).getDateValue();
132: } else if (component instanceof JComboBox)
133: return ((JComboBox) component).getSelectedItem();
134: else if (component instanceof JSpinner) {
135: return SpinEditor.getValue((JSpinner) component, field);
136: }
137: return null;
138: }
139:
140: static void markComponentAsUnmodified(JComponent component) {
141: component.setForeground(Color.BLACK);
142: verifiedComponent(component).setForeground(Color.BLACK);
143: }
144:
145: public static void setValueOfComponent(JComponent component,
146: Object value, boolean readOnly) {
147: boolean isMultipleValues = ClassUtils.isMultipleValue(value);
148: component = getFieldComponent(component);
149: if (component instanceof JTextField)
150: ((JTextField) component).setText((value == null) ? ""
151: : (isMultipleValues ? "" : value.toString()));
152: else if (component instanceof JTextArea)
153: ((JTextArea) component).setText((value == null) ? ""
154: : (isMultipleValues ? "" : value.toString()));
155: // ((JTextArea)component).setText(isMultipleValues ? "" : value.toString());
156: //TODO fix?
157: else if (component instanceof JCheckBox)
158: ((JCheckBox) component).setSelected(value == null ? false
159: : ((Boolean) value).booleanValue());
160: else if (component instanceof ExtDateField) {
161: if (DateTime.getZeroDate().equals(value))
162: value = null;
163: ((ExtDateField) component).setValue((value == null) ? null
164: : value);
165: } else if (component instanceof JComboBox)
166: ((JComboBox) component).setSelectedItem(value);
167: else if (component instanceof JSpinner && value != null) {
168: ((JSpinner) component).setValue(value);
169: if (isMultipleValues) { // set editor to empty. Unfortunately, this disables the spinner
170: getSpinnerTextField((JSpinner) component).setText("");
171: } else {
172: //TODO make escape key work properly by putting back original value
173: // getSpinnerTextField((JSpinner)component).setText(value.toString());
174: // System.out.println("setting spinner text " + value);
175: }
176: } else if (component instanceof LinkLabel) {
177: ((LinkLabel) component).setHyperlink((Hyperlink) value);
178:
179: } else if (component instanceof JLabel) {
180: ((JLabel) component).setText(value == null ? "" : value
181: .toString());
182: } else if (component instanceof LookupField) {
183: ((LookupField) component).setText(value == null ? ""
184: : value.toString());
185:
186: }
187:
188: component.setEnabled(!readOnly);
189: markComponentAsUnmodified(component);
190: }
191:
192: // component = valueHoldingComponent(component);
193:
194: /**
195: *
196: */
197: public ComponentFactory() {
198: super ();
199: // TODO Auto-generated constructor stub
200: }
201:
202: private static JComponent componentFor(final Field field,
203: Object value, boolean readOnly) {
204: JComponent component = null;
205: Range range = field.getRange();
206: JTextComponent text = null;
207: if (value instanceof Boolean) {
208: component = new JCheckBox(field.getName(),
209: ((Boolean) value).booleanValue());
210: } else if (readOnly) {
211: if (field.isHyperlink())
212: component = new LinkLabel((Hyperlink) value);
213: else
214: component = new JLabel();
215:
216: } else if (field.isDate()) {
217: ExtDateField d = createDateField(field);
218: Object o = d.getComponents();
219: d.getFormattedTextField().addActionListener(
220: new FieldVerifier.VerifierListener());
221: component = d;
222: text = (JTextComponent) verifiedComponent(component);
223: // text.setEnabled(false);
224: } else if (field.getLookupTypes() != null) {
225: component = new LookupField(field, null);
226: } else if (field.hasOptions()) {
227: final JComboBox combo = new JComboBox(field
228: .getOptions(null));
229: // if ("Field.accessControlPolicy".equals(field.getId())){
230: // combo.setInputVerifier(new InputVerifier(){
231: // @Override
232: // public boolean verify(JComponent input) {
233: // return Alert.okCancel(Messages.getString("Text.resetRoles"));
234: // }
235: // });
236: // }
237:
238: // else
239: combo
240: .addActionListener(new FieldVerifier.VerifierListener());
241: component = combo;
242: // if the combo can change dynamically, need to rebuild the combo dynamically
243: if (field.isDynamicOptions()) {
244: combo.getComponent(0).addMouseListener(
245: new MouseListener() {
246:
247: public void mouseClicked(MouseEvent arg0) {
248: }
249:
250: public void mouseEntered(MouseEvent arg0) {
251: }
252:
253: public void mouseExited(MouseEvent arg0) {
254: }
255:
256: public void mousePressed(MouseEvent arg0) {
257: combo
258: .setModel(new DefaultComboBoxModel(
259: field.getOptions(null)));
260: combo.showPopup();
261: }
262:
263: public void mouseReleased(MouseEvent arg0) {
264: }
265: });
266:
267: }
268:
269: } else if (range != null) {
270: component = SpinEditor.getJSpinnerInstance(field,
271: ((Number) value).doubleValue(), false);
272: final JSpinner spinner = (JSpinner) component;
273: text = getSpinnerTextField(spinner);
274: final JTextComponent t = text;
275: ((JSpinner) component)
276: .addChangeListener(new ChangeListener() {
277:
278: public void stateChanged(ChangeEvent arg0) {
279: FieldVerifier v = (FieldVerifier) t
280: .getInputVerifier();
281: boolean same = true;
282: if (v != null) {
283: Number spinnerValue = (Number) spinner
284: .getValue();
285: if (spinnerValue == null)
286: return;
287: double spin = MathUtils
288: .roundToDecentPrecision(spinnerValue
289: .doubleValue());
290: if (v == null || v.getValue() == null)
291: same = false;
292: else
293: same = spin == ((Number) v
294: .getValue()).doubleValue();
295: }
296: t.setForeground(same ? Color.BLACK
297: : Color.BLUE);
298: }
299: });
300: } else {
301: if (field.isMemo()) {
302: text = new JTextArea();
303: component = new JScrollPane(text);
304: } else {
305: text = new JTextField();
306: int width = field.getTextWidth(null, null);
307: if (width != Integer.MAX_VALUE) {
308: ((AbstractDocument) text.getDocument())
309: .setDocumentFilter(new FixedSizeFilter(
310: width));
311: }
312:
313: component = text;
314: }
315: }
316: if (text != null) {
317: text.addKeyListener(new KeyListener() {
318: public void keyPressed(KeyEvent arg0) {
319: }
320:
321: public void keyReleased(KeyEvent arg0) {
322: }
323:
324: public void keyTyped(KeyEvent arg0) {
325: JTextComponent textComponent = (JTextComponent) arg0
326: .getComponent();
327: textComponent.setForeground(Color.BLUE);
328: FieldDialog parentFieldDialog = getParentFieldDialog(textComponent);
329: if (parentFieldDialog != null)
330: parentFieldDialog
331: .setDirtyComponent(textComponent);
332: }
333: });
334:
335: }
336: if (!(component instanceof JCheckBox))
337: setValueOfComponent(component, value, readOnly);
338: return component;
339:
340: }
341:
342: public static FieldDialog getParentFieldDialog(Component c) {
343: while (c != null && !(c instanceof FieldDialog))
344: c = c.getParent();
345: return (FieldDialog) c;
346: }
347:
348: public static Object getFieldValue(Field field, ObjectRef objectRef) {
349: if (field.hasOptions())
350: return field.getText(objectRef, context);
351: Object value = field.getValue(objectRef, context);
352: if (value != null && field.isDate()) {
353: value = new Date(DateTime
354: .dayFloor(((Date) value).getTime()));
355: }
356: return value;
357:
358: }
359:
360: public static void updateValueOfComponent(JComponent component,
361: Field field, ObjectRef objectRef) {
362: Object value;
363: boolean readOnly = component instanceof JLabel /*|| field.isObjectReadOnly(objectRef.getObject())*/
364: || field.isReadOnly(objectRef, context);
365: // This is ugly
366: if (component instanceof LinkLabel) {
367: value = field.getValue(objectRef, context);
368: } else if (component instanceof LookupField) {
369: value = field.getValue(objectRef, context);
370: } else if (component instanceof JLabel) {
371: value = field.getText(objectRef, context);
372: } else if (field.hasOptions()
373: && field.getSelect() instanceof StaticSelect) {
374: value = field.getText(objectRef, context);
375: if (value instanceof String
376: && ((String) value).length() == 0)
377: value = null;
378: } else if (field.isDuration() || field.isRate()) {
379: value = field.getText(objectRef, context);
380: } else {
381: value = field.getValue(objectRef, context);
382: }
383: if (!readOnly && field.isDate() && value != null) {
384: if (!(value instanceof Date)) {
385: System.out.println("bad date");
386: value = field.getValue(objectRef, context);
387: return;
388: }
389: }
390:
391: JComponent verifiedComponent = verifiedComponent(component);
392: FieldVerifier verifier = (FieldVerifier) verifiedComponent
393: .getInputVerifier();
394: if (verifier != null)
395: verifier.setUpdating(true);
396: setValueOfComponent(component, value, readOnly);
397:
398: // Need to update cached value of field verifier also
399: if (verifier != null) {
400: verifier.setValue(value);
401: verifier.setUpdating(false);
402: }
403: markComponentAsUnmodified(component);
404: }
405:
406: static JComponent verifiedComponent(JComponent component) {
407: if (component instanceof ExtDateField) { // The editor component is what we want
408: return (JComponent) ((ExtDateField) component)
409: .getFormattedTextField();
410: } else if (component instanceof JSpinner) { // spinners are strange. See http://mindprod.com/jgloss/focus.html
411: return getSpinnerTextField((JSpinner) component);
412: } else if (component instanceof LookupField) {
413: return ((LookupField) component).getDisplay();
414: } else {
415: return component; // for normal fields, the component itself is verified
416: }
417: }
418:
419: /**
420: * Use java 1.4s InputVerifier class to check value on focus loss
421: * @param component
422: * @param verifier
423: */
424: private static void setVerifier(JComponent component,
425: FieldVerifier verifier) {
426: component = getFieldComponent(component);
427: JComponent verifiedComponent = verifiedComponent(component);
428: verifiedComponent.setInputVerifier(verifier);
429: }
430:
431: /** Get the componenet to use for the field
432: *
433: * @param field
434: * @param objectRef
435: * @param flag can be READ_ONLY to force the field as a label, or SOMETIMES_READ_ONLY, in which case the component will
436: * not be a label even if the field is read only. value can also be 0 for default case
437: * @return
438: */
439: public static JComponent componentFor(Field field,
440: ObjectRef objectRef, int flag) {
441: Object value = getFieldValue(field, objectRef);
442:
443: boolean readOnly = (flag & READ_ONLY) != 0;
444: boolean sometimesReadOnly = (flag & SOMETIMES_READ_ONLY) != 0;
445: boolean fieldReadOnly = field.isReadOnly(objectRef, context);
446: readOnly |= fieldReadOnly;
447: if (sometimesReadOnly)
448: readOnly = false;
449: JComponent component = componentFor(field, value, readOnly);
450: if (component instanceof LookupField) { //checkboxes update immediately on clicking
451: ((LookupField) component)
452: .addChangeListener(new FieldChangeListener(field,
453: objectRef));
454: } else if (!readOnly) {
455: if (component instanceof JCheckBox) { //checkboxes update immediately on clicking
456: ((JCheckBox) component)
457: .addItemListener(new FieldChangeListener(field,
458: objectRef));
459: } else {
460: //An exception for accessControlPolicy to have the correct behaviour
461: if ("Field.accessControlPolicy".equals(field.getId())) { //TODO remove this hack and do it properly. This code should all be generic
462: component.setInputVerifier(new FieldVerifier(field,
463: objectRef, getValueFromComponent(component,
464: field)) {
465: public boolean verify(JComponent component) {
466: final JComboBox c = (JComboBox) component;
467: Object newValue = (Object) ComponentFactory
468: .getValueFromComponent(component,
469: field);
470: Object publicValue = c
471: .getItemAt(AccessControlPolicy.PUBLIC);
472: if (newValue != value) {
473: Integer oldValue = (Integer) field
474: .getValue(objectRef, context);
475: try {
476: //testing=true;
477: if (oldValue == AccessControlPolicy.RESTRICTED
478: && publicValue
479: .equals(newValue)) {
480: if (Alert
481: .okCancel(Messages
482: .getString("Text.resetRoles"))) {
483: Project project = (Project) objectRef
484: .getObject();
485: project.resetRoles(true);
486: field.setValue(objectRef,
487: source, newValue,
488: context);
489: return true;
490: } else {
491: c
492: .setSelectedIndex(oldValue);
493: return false;
494: }
495: } else
496: field.setValue(objectRef,
497: source, newValue,
498: context);
499: } catch (FieldParseException e) {//never happen
500: }
501: }
502: return true;
503: }
504:
505: public boolean shouldYieldFocus(
506: JComponent component) {
507: return true;
508: }
509:
510: });
511: } else
512: setVerifier(component, new FieldVerifier(field,
513: objectRef, getValueFromComponent(component,
514: field)));
515: }
516: } else {
517: if (component instanceof JLabel) {
518: ((JLabel) component).setText(field.getText(objectRef,
519: context));
520: }
521: }
522: // if field may be read only, set its status based on field read only
523: if (sometimesReadOnly && fieldReadOnly)
524: component.setEnabled(false);
525: return component;
526: }
527:
528: public static ExtDateField createDateField() {
529: return createDateField(null);
530: }
531:
532: public static ExtDateField createDateField(Field field) {
533: long date = DateTime.midnightToday();
534: DateFormat format;
535: if (field != null) {
536: if (field.isDateOnly())
537: format = EditOption.getInstance().getShortDateFormat();
538: else
539: format = EditOption.getInstance().getDateFormat();
540: if (field.isStartValue())
541: date = CalendarOption.getInstance().makeValidStart(
542: date, true);
543: else if (field.isEndValue())
544: date = CalendarOption.getInstance().makeValidEnd(date,
545: true);
546: } else {
547: format = EditOption.getInstance().getShortDateFormat();
548: }
549: ExtDateField df = new ExtDateField(format);
550: // df.setValue(new Date(date));
551: df.getFormattedTextField().addPropertyChangeListener(
552: new PropertyChangeListener() {
553:
554: public void propertyChange(PropertyChangeEvent evt) {
555: JFormattedTextField field = (JFormattedTextField) evt
556: .getSource();
557: if (evt.getPropertyName().equals("value")
558: && evt.getNewValue() != evt
559: .getOldValue()) {
560: if (field.getInputVerifier() != null)
561: field.getInputVerifier().verify(field);
562: }
563: }
564: });
565: return df;
566: }
567:
568: }
|