/* The contents of this file are subject to the Common Public Attribution License Version 1.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.projity.com/license . The License is based on the Mozilla Public License Version 1.1 but Sections 14 and 15 have been added to cover use of software over a computer network and provide for limited attribution for the Original Developer. In addition, Exhibit A has been modified to be consistent with Exhibit B. Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. The Original Code is OpenProj. The Original Developer is the Initial Developer and is Projity, Inc. All portions of the code written by Projity are Copyright (c) 2006, 2007. All Rights Reserved. Contributors Projity, Inc. Alternatively, the contents of this file may be used under the terms of the Projity End-User License Agreeement (the Projity License), in which case the provisions of the Projity License are applicable instead of those above. If you wish to allow use of your version of this file only under the terms of the Projity License and not to allow others to use your version of this file under the CPAL, indicate your decision by deleting the provisions above and replace them with the notice and other provisions required by the Projity License. If you do not delete the provisions above, a recipient may use your version of this file under either the CPAL or the Projity License. [NOTE: The text of this license may differ slightly from the text of the notices in Exhibits A and B of the license at http://www.projity.com/license. You should use the latest text at http://www.projity.com/license for your modifications. You may not remove this license text from the source files.] Attribution Information: Attribution Copyright Notice: Copyright � 2006, 2007 Projity, Inc. Attribution Phrase (not exceeding 10 words): Powered by OpenProj, an open source solution from Projity. Attribution URL: http://www.projity.com Graphic Image as provided in the Covered Code as file: openproj_logo.png with alternatives listed on http://www.projity.com/logo Display of Attribution Information is required in Larger Works which are defined in the CPAL as a work which combines Covered Code or portions thereof with code not governed by the terms of the CPAL. However, in addition to the other notice obligations, all copies of the Covered Code in Executable and Source Code form distributed must, as a form of attribution of the original author, include on each user interface screen the "OpenProj" logo visible to all users. The OpenProj logo should be located horizontally aligned with the menu bar and left justified on the top left of the screen adjacent to the File menu. The logo must be at least 100 x 25 pixels. When users click on the "OpenProj" logo it must direct them back to http://www.projity.com. */ package com.projity.dialog.util; import java.awt.Color; import java.awt.Component; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.text.DateFormat; import java.util.Date; import javax.swing.DefaultComboBoxModel; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JFormattedTextField; import javax.swing.JLabel; import javax.swing.JScrollPane; import javax.swing.JSpinner; import javax.swing.JTextArea; import javax.swing.JTextField; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.text.AbstractDocument; import javax.swing.text.JTextComponent; import com.projity.datatype.Hyperlink; import com.projity.dialog.FieldDialog; import com.projity.field.Field; import com.projity.field.FieldContext; import com.projity.field.FieldParseException; import com.projity.field.ObjectRef; import com.projity.field.Range; import com.projity.field.StaticSelect; import com.projity.options.CalendarOption; import com.projity.options.EditOption; import com.projity.pm.graphic.spreadsheet.editor.SpinEditor; import com.projity.pm.task.AccessControlPolicy; import com.projity.pm.task.Project; import com.projity.strings.Messages; import com.projity.util.Alert; import com.projity.util.ClassUtils; import com.projity.util.DateTime; import com.projity.util.MathUtils; /** * */ public class ComponentFactory { public static final int READ_ONLY = 1; public static final int SOMETIMES_READ_ONLY = 2; private static double MAX_VALUE = 60000000.0; private static FieldContext context = null; private static JComponent getFieldComponent(JComponent component) { if (component instanceof JScrollPane) component = (JComponent) ((JScrollPane)component).getViewport().getComponent(0); return component; } static JTextField getSpinnerTextField(JSpinner spinner) { return ((JSpinner.DefaultEditor) spinner.getEditor()).getTextField(); } public static Object getValueFromComponent(JComponent component, Field field) { component = getFieldComponent(component); if (component instanceof JTextField) return ((JTextField)component).getText(); else if (component instanceof JTextArea) return ((JTextArea)component).getText(); else if (component instanceof JCheckBox) return new Boolean(((JCheckBox)component).isSelected()); else if (component instanceof ExtDateField) { return ((ExtDateField)component).getDateValue(); } else if (component instanceof JComboBox) return ((JComboBox)component).getSelectedItem(); else if (component instanceof JSpinner) { return SpinEditor.getValue((JSpinner)component,field); } return null; } static void markComponentAsUnmodified(JComponent component) { component.setForeground(Color.BLACK); verifiedComponent(component).setForeground(Color.BLACK); } public static void setValueOfComponent(JComponent component, Object value, boolean readOnly) { boolean isMultipleValues = ClassUtils.isMultipleValue(value); component = getFieldComponent(component); if (component instanceof JTextField) ((JTextField)component).setText((value==null)?"":(isMultipleValues ? "" : value.toString())); else if (component instanceof JTextArea) ((JTextArea)component).setText((value==null)?"":(isMultipleValues ? "" : value.toString())); // ((JTextArea)component).setText(isMultipleValues ? "" : value.toString()); //TODO fix? else if (component instanceof JCheckBox) ((JCheckBox)component).setSelected(value == null ? false : ((Boolean)value).booleanValue()); else if (component instanceof ExtDateField) { if (DateTime.getZeroDate().equals(value)) value = null; ((ExtDateField)component).setValue((value==null)?null:value); } else if (component instanceof JComboBox) ((JComboBox)component).setSelectedItem(value); else if (component instanceof JSpinner&&value!=null) { ((JSpinner)component).setValue(value); if (isMultipleValues) { // set editor to empty. Unfortunately, this disables the spinner getSpinnerTextField((JSpinner)component).setText(""); } else { //TODO make escape key work properly by putting back original value // getSpinnerTextField((JSpinner)component).setText(value.toString()); // System.out.println("setting spinner text " + value); } } else if (component instanceof LinkLabel) { ((LinkLabel)component).setHyperlink((Hyperlink) value); }else if (component instanceof JLabel) { ((JLabel)component).setText(value == null ? "" : value.toString()); } else if (component instanceof LookupField) { ((LookupField)component).setText(value == null ? "" : value.toString()); } component.setEnabled(!readOnly); markComponentAsUnmodified(component); } // component = valueHoldingComponent(component); /** * */ public ComponentFactory() { super(); // TODO Auto-generated constructor stub } private static JComponent componentFor(final Field field, Object value, boolean readOnly) { JComponent component = null; Range range = field.getRange(); JTextComponent text = null; if (value instanceof Boolean) { component = new JCheckBox(field.getName(), ((Boolean)value).booleanValue()); } else if (readOnly) { if (field.isHyperlink()) component = new LinkLabel((Hyperlink)value); else component = new JLabel(); } else if (field.isDate()) { ExtDateField d = createDateField(field); Object o = d.getComponents(); d.getFormattedTextField().addActionListener(new FieldVerifier.VerifierListener()); component = d; text = (JTextComponent) verifiedComponent(component); // text.setEnabled(false); } else if (field.getLookupTypes() != null) { component = new LookupField(field,null); } else if (field.hasOptions()) { final JComboBox combo = new JComboBox(field.getOptions(null)); // if ("Field.accessControlPolicy".equals(field.getId())){ // combo.setInputVerifier(new InputVerifier(){ // @Override // public boolean verify(JComponent input) { // return Alert.okCancel(Messages.getString("Text.resetRoles")); // } // }); // } // else combo.addActionListener(new FieldVerifier.VerifierListener()); component = combo; // if the combo can change dynamically, need to rebuild the combo dynamically if (field.isDynamicOptions()) { combo.getComponent(0).addMouseListener(new MouseListener() { public void mouseClicked(MouseEvent arg0) { } public void mouseEntered(MouseEvent arg0) { } public void mouseExited(MouseEvent arg0) { } public void mousePressed(MouseEvent arg0) { combo.setModel(new DefaultComboBoxModel(field.getOptions(null))); combo.showPopup(); } public void mouseReleased(MouseEvent arg0) { }}); } } else if (range != null) { component = SpinEditor.getJSpinnerInstance(field,((Number)value).doubleValue(),false); final JSpinner spinner = (JSpinner)component; text = getSpinnerTextField(spinner); final JTextComponent t = text; ((JSpinner)component).addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent arg0) { FieldVerifier v = (FieldVerifier)t.getInputVerifier(); boolean same = true; if (v != null) { Number spinnerValue = (Number) spinner.getValue(); if (spinnerValue == null) return; double spin = MathUtils.roundToDecentPrecision(spinnerValue.doubleValue()); if (v == null || v.getValue() == null) same = false; else same = spin == ((Number)v.getValue()).doubleValue(); } t.setForeground(same ? Color.BLACK : Color.BLUE); }}); } else { if (field.isMemo()) { text = new JTextArea(); component = new JScrollPane(text); } else { text = new JTextField(); int width = field.getTextWidth(null,null); if (width != Integer.MAX_VALUE) { ((AbstractDocument)text.getDocument()).setDocumentFilter(new FixedSizeFilter(width)); } component = text; } } if (text != null) { text.addKeyListener(new KeyListener() { public void keyPressed(KeyEvent arg0) { } public void keyReleased(KeyEvent arg0) { } public void keyTyped(KeyEvent arg0) { JTextComponent textComponent = (JTextComponent)arg0.getComponent(); textComponent.setForeground(Color.BLUE); FieldDialog parentFieldDialog = getParentFieldDialog(textComponent); if (parentFieldDialog != null) parentFieldDialog.setDirtyComponent(textComponent); }}); } if (!(component instanceof JCheckBox)) setValueOfComponent(component,value,readOnly); return component; } public static FieldDialog getParentFieldDialog(Component c) { while (c != null && !(c instanceof FieldDialog)) c = c.getParent(); return (FieldDialog) c; } public static Object getFieldValue(Field field,ObjectRef objectRef) { if (field.hasOptions() ) return field.getText(objectRef,context); Object value = field.getValue(objectRef,context); if (value != null && field.isDate()) { value = new Date(DateTime.dayFloor(((Date)value).getTime())); } return value; } public static void updateValueOfComponent(JComponent component, Field field, ObjectRef objectRef) { Object value; boolean readOnly = component instanceof JLabel /*|| field.isObjectReadOnly(objectRef.getObject())*/ || field.isReadOnly(objectRef,context); // This is ugly if (component instanceof LinkLabel) { value = field.getValue(objectRef,context); } else if (component instanceof LookupField) { value = field.getValue(objectRef,context); } else if (component instanceof JLabel) { value = field.getText(objectRef,context); } else if (field.hasOptions() && field.getSelect() instanceof StaticSelect) { value = field.getText(objectRef,context); if (value instanceof String && ((String)value).length() == 0) value = null; } else if (field.isDuration() || field.isRate()) { value = field.getText(objectRef,context); } else { value = field.getValue(objectRef,context); } if (!readOnly && field.isDate() && value != null) { if (!(value instanceof Date)) { System.out.println("bad date"); value = field.getValue(objectRef,context); return; } } JComponent verifiedComponent = verifiedComponent(component); FieldVerifier verifier = (FieldVerifier) verifiedComponent.getInputVerifier(); if (verifier != null) verifier.setUpdating(true); setValueOfComponent(component,value,readOnly); // Need to update cached value of field verifier also if (verifier != null) { verifier.setValue(value); verifier.setUpdating(false); } markComponentAsUnmodified(component); } static JComponent verifiedComponent(JComponent component) { if (component instanceof ExtDateField) { // The editor component is what we want return (JComponent) ((ExtDateField)component).getFormattedTextField(); } else if (component instanceof JSpinner) { // spinners are strange. See http://mindprod.com/jgloss/focus.html return getSpinnerTextField((JSpinner)component); } else if (component instanceof LookupField) { return ((LookupField)component).getDisplay(); } else { return component; // for normal fields, the component itself is verified } } /** * Use java 1.4s InputVerifier class to check value on focus loss * @param component * @param verifier */ private static void setVerifier(JComponent component, FieldVerifier verifier) { component = getFieldComponent(component); JComponent verifiedComponent = verifiedComponent(component); verifiedComponent.setInputVerifier(verifier); } /** Get the componenet to use for the field * * @param field * @param objectRef * @param flag can be READ_ONLY to force the field as a label, or SOMETIMES_READ_ONLY, in which case the component will * not be a label even if the field is read only. value can also be 0 for default case * @return */ public static JComponent componentFor(Field field, ObjectRef objectRef, int flag) { Object value = getFieldValue(field,objectRef); boolean readOnly = (flag & READ_ONLY) != 0; boolean sometimesReadOnly = (flag & SOMETIMES_READ_ONLY) != 0; boolean fieldReadOnly = field.isReadOnly(objectRef,context); readOnly |= fieldReadOnly; if (sometimesReadOnly) readOnly = false; JComponent component = componentFor(field,value,readOnly); if (component instanceof LookupField) { //checkboxes update immediately on clicking ((LookupField)component).addChangeListener(new FieldChangeListener(field,objectRef)); } else if (!readOnly) { if (component instanceof JCheckBox) { //checkboxes update immediately on clicking ((JCheckBox)component).addItemListener(new FieldChangeListener(field,objectRef)); } else { //An exception for accessControlPolicy to have the correct behaviour if ("Field.accessControlPolicy".equals(field.getId())){ //TODO remove this hack and do it properly. This code should all be generic component.setInputVerifier(new FieldVerifier(field,objectRef, getValueFromComponent(component, field)){ public boolean verify(JComponent component) { final JComboBox c=(JComboBox)component; Object newValue = (Object)ComponentFactory.getValueFromComponent(component, field); Object publicValue=c.getItemAt(AccessControlPolicy.PUBLIC); if (newValue != value){ Integer oldValue=(Integer)field.getValue(objectRef, context); try { //testing=true; if (oldValue==AccessControlPolicy.RESTRICTED&&publicValue.equals(newValue)){ if(Alert.okCancel(Messages.getString("Text.resetRoles"))){ Project project=(Project)objectRef.getObject(); project.resetRoles(true); field.setValue(objectRef,source,newValue,context); return true; }else{ c.setSelectedIndex(oldValue); return false; } }else field.setValue(objectRef,source,newValue,context); } catch (FieldParseException e) {//never happen } } return true; } public boolean shouldYieldFocus(JComponent component) { return true; } }); } else setVerifier(component,new FieldVerifier(field,objectRef, getValueFromComponent(component, field))); } } else { if (component instanceof JLabel) { ((JLabel)component).setText(field.getText(objectRef,context)); } } // if field may be read only, set its status based on field read only if (sometimesReadOnly && fieldReadOnly) component.setEnabled(false); return component; } public static ExtDateField createDateField() { return createDateField(null); } public static ExtDateField createDateField(Field field) { long date = DateTime.midnightToday(); DateFormat format; if (field != null) { if (field.isDateOnly()) format = EditOption.getInstance().getShortDateFormat(); else format = EditOption.getInstance().getDateFormat(); if (field.isStartValue()) date = CalendarOption.getInstance().makeValidStart(date, true); else if (field.isEndValue()) date = CalendarOption.getInstance().makeValidEnd(date, true); } else { format = EditOption.getInstance().getShortDateFormat(); } ExtDateField df = new ExtDateField(format); // df.setValue(new Date(date)); df.getFormattedTextField().addPropertyChangeListener(new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent evt) { JFormattedTextField field = (JFormattedTextField)evt.getSource(); if (evt.getPropertyName().equals("value") && evt.getNewValue() != evt.getOldValue()) { if (field.getInputVerifier() != null) field.getInputVerifier().verify(field); } }}); return df; } }