package org.openswing.swing.client; import java.beans.*; import java.util.*; import javax.swing.*; import org.openswing.swing.logger.client.*; import org.openswing.swing.message.receive.java.*; import org.openswing.swing.properties.client.*; import org.openswing.swing.util.client.*; import org.openswing.swing.util.java.*; /** * <p>Title: OpenSwing Framework</p> * <p>Description: Grid control having two columns: property name and property value, * where each row (each property) has its own data type (text, numeric, date, check-box, lookup, etc.), expressed as input control. * Hence this control can be used to set a collection of properties. * Note that PropertyGridControl is not an input control so it cannot be added to a Form panel.</p> * <p>Copyright: Copyright (C) 2006 Mauro Carniel</p> * * <p> This file is part of OpenSwing Framework. * This library is free software; you can redistribute it and/or * modify it under the terms of the (LGPL) Lesser General Public * License as published by the Free Software Foundation; * * GNU LESSER GENERAL PUBLIC LICENSE * Version 2.1, February 1999 * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * * The author may be contacted at: * maurocarniel@tin.it</p> * * @author Mauro Carniel * @version 1.0 */ public class PropertyGridControl extends JScrollPane implements DataController { /** editable table model having a specific cell editor for each row */ private PropertyGridModel model = new PropertyGridModel(); /** table that contains properties */ private JTable grid = new JTable(model); /** button linked to the grid, used to set INSERT mode (insert new row on grid) */ private InsertButton insertButton = null; /** button linked to the grid, used to set EDIT mode (edit the selected row on grid) */ private EditButton editButton = null; /** button linked to the grid, used to set READONLY mode and to reload the TableModel */ private ReloadButton reloadButton = null; /** button linked to the grid, used to commit the modified rows (on INSERT/EDIT mode) */ private SaveButton saveButton = null; /** controller used to fetch data (property values) */ private PropertyGridController controller = new PropertyGridController(); /** current enabled button state, before GenericButton.setEnabled method calling */ private HashMap currentValueButtons = new HashMap(); /** collection of buttons binded to grid (InsertButton, EditButton, etc) */ private HashSet bindedButtons = new HashSet(); public PropertyGridControl() { try { jbInit(); grid.getColumnModel().removeColumn(grid.getColumnModel().getColumn(2)); // remove input control column... grid.getColumnModel().removeColumn(grid.getColumnModel().getColumn(2)); // remove default value column... grid.getColumnModel().getColumn(1).setCellEditor(new PropertyCellEditor()); grid.getColumnModel().getColumn(1).setCellRenderer(new PropertyCellRenderer()); grid.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); grid.setAutoResizeMode(grid.AUTO_RESIZE_OFF); grid.setRowHeight(ClientSettings.CELL_HEIGHT); grid.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); grid.setSurrendersFocusOnKeystroke(true); grid.setBackground(ClientSettings.GRID_CELL_BACKGROUND); grid.setForeground(ClientSettings.GRID_CELL_FOREGROUND); grid.setSelectionBackground(ClientSettings.GRID_SELECTION_BACKGROUND); grid.setSelectionForeground(ClientSettings.GRID_SELECTION_FOREGROUND); } catch(Exception e) { e.printStackTrace(); } } /** * Set current enabled value of button. * @param button generic button that fires this event * @param currentValue current enabled value */ public final void setCurrentValue(GenericButton button,boolean currentValue) { currentValueButtons.put(button,new Boolean(currentValue)); } /** * @param button generic button * @return current enabled value for the specified button */ public final boolean getCurrentValue(GenericButton button) { Boolean oldValue = (Boolean)currentValueButtons.get(button); if (oldValue==null) return true; else return oldValue.booleanValue(); } private void jbInit() throws Exception { this.getViewport().add(grid, null); } /** * @return current grid mode */ public final int getMode() { return model.getMode(); } /** * Set grid mode. * @param mode grid mode; possibile values: READONLY, INSERT, EDIT */ public final void setMode(int mode) { model.setMode(mode); grid.repaint(); } /** * Clear grid content. */ public final void clearData() { while(model.getRowCount()>0) model.removeRow(0); grid.revalidate(); grid.repaint(); } /** * Add a property to the grid. * @param propertyDescription description of the property; this is NOT automatically translated: use ClientSettings.getInstance().getResources().getResource() to translate it * @param inputControl type of property; this input control must have setted "attributeName" and "required" properties * @param defaultValue default value, used when the grid is in INSERT mode * @param userObject additional info (optional) * @return <code>false</code> if the specified attribute name has already been used for another row, <code>true</code> otherwise */ public final boolean addProperty(String propertyDescription,InputControl inputControl,Object defaultValue,Object userObject) { model.addRow(new Object[]{propertyDescription,null,inputControl,defaultValue,userObject}); return true; } /** * @param attributeName attribute name that identify a row * @return row index related to the specified attribute name */ public final int findRow(String attributeName) { int rowIndex = model.findRow(attributeName); if (rowIndex==-1) Logger.error(this.getClass().getName(), "findRow", "The specified attribute '"+attributeName+"' does not exist.",null); return rowIndex; } /** * * @param attributeName attribute name that identify the row * @param value property value to set * @return <code>false</code> if the specified attribute name has not been found in the model, <code>true</code> otherwise */ public final boolean setPropertyValue(String attributeName,Object value) { int rowIndex = findRow(attributeName); if (rowIndex==-1) return false; setPropertyValue(rowIndex,value); return true; } /** * @param rowIndex row index in the model * @param value property value to set */ public final void setPropertyValue(int rowIndex,Object value) { model.setPropertyValue(rowIndex,value); } /** * @param attributeName attribute name that identify the row * @return property value related to the specified attribute name */ public final Object getPropertyValue(String attributeName) { int rowIndex = findRow(attributeName); if (rowIndex==-1) return null; return getPropertyValue(rowIndex); } /** * @param rowIndex row index in the model * @return property value related to the specified attribute name */ public final Object getPropertyValue(int rowIndex) { return model.getPropertyValue(rowIndex); } /** * @param attributeName attribute name that identify the row * @return property value related to the specified attribute name */ public final Object getOldPropertyValue(String attributeName) { int rowIndex = findRow(attributeName); if (rowIndex==-1) return null; return getOldPropertyValue(rowIndex); } /** * @param rowIndex row index in the model * @return property value related to the specified attribute name */ public final Object getOldPropertyValue(int rowIndex) { return model.getOldPropertyValue(rowIndex); } /** * @param attributeName attribute name that identify the row * @return user object related to the specified attribute name */ public final Object getUserObject(String attributeName) { int rowIndex = findRow(attributeName); if (rowIndex==-1) return null; return getUserObject(rowIndex); } /** * @param rowIndex row index in the model * @return user object related to the specified row index */ public final Object getUserObject(int rowIndex) { return model.getUserObject(rowIndex); } /** * @param attributeName attribute name that identify the row * @return input control related to the specified row index */ public final InputControl getInputControl(String attributeName) { int rowIndex = findRow(attributeName); if (rowIndex==-1) return null; return getInputControl(rowIndex); } /** * @param rowIndex row index in the model * @return input control related to the specified row index */ public final InputControl getInputControl(int rowIndex) { return model.getInputControl(rowIndex); } /** * Add a set properties to the grid, retrieving them from the specified value object: * for each attribute defined in the v.o. will be created a row (a property) in the grid. * Also attribute values are retrieved from the v.o. and set in the grid as property values. * All properties are defined as mandatory. * @param valueObject value object used to create a set of properties, whose description is determined by translating attribute names */ public final void addProperties(ValueObject valueObject) { try { PropertyDescriptor[] props = Introspector.getBeanInfo(valueObject.getClass()).getPropertyDescriptors(); InputControl inputControl = null; for(int i=0;i<props.length;i++) { inputControl = null; if (props[i].getPropertyType().equals(java.util.Date.class) || props[i].getPropertyType().equals(java.sql.Date.class) || props[i].getPropertyType().equals(java.sql.Timestamp.class)) inputControl = new DateControl(); else if (props[i].getPropertyType().equals(Integer.class) || props[i].getPropertyType().equals(Short.class) || props[i].getPropertyType().equals(Long.class) || props[i].getPropertyType().equals(Integer.TYPE) || props[i].getPropertyType().equals(Short.TYPE) || props[i].getPropertyType().equals(Long.TYPE)) { inputControl = new NumericControl(); ((NumericControl)inputControl).setDecimals(0); } else if (props[i].getPropertyType().equals(Float.class) || props[i].getPropertyType().equals(Double.class) || props[i].getPropertyType().equals(Float.TYPE) || props[i].getPropertyType().equals(Double.TYPE) || props[i].getPropertyType().equals(java.math.BigDecimal.class)) { inputControl = new NumericControl(); ((NumericControl)inputControl).setDecimals(5); } else if (props[i].getPropertyType().equals(byte[].class)) inputControl = new ImageControl(); else if (props[i].getPropertyType().equals(String.class)) inputControl = new TextControl(); if (inputControl!=null) { inputControl.setAttributeName(props[i].getName()); model.addRow(new Object[]{ ClientSettings.getInstance().getResources().getResource(props[i].getName()), props[i].getReadMethod().invoke(valueObject,new Object[0]), inputControl, props[i].getReadMethod().invoke(valueObject,new Object[0]), null }); } } } catch (Exception ex) { Logger.error(this.getClass().getName(), "addProperties", ex.getMessage(), ex); } } /** * Set property values, based on the specified value object: * for each attribute defined in the v.o. that matches with a row in the grid, its value will be retrieved and set in the grid. */ public final void setProperties(ValueObject valueObject) { try { PropertyDescriptor[] props = Introspector.getBeanInfo(valueObject.getClass()).getPropertyDescriptors(); int rowIndex = -1; for(int i=0;i<props.length;i++) { rowIndex = model.findRow(props[i].getName()); if (rowIndex!=-1) setPropertyValue(rowIndex,props[i].getReadMethod().invoke(valueObject,new Object[0])); } } catch (Exception ex) { Logger.error(this.getClass().getName(), "setProperties", ex.getMessage(), ex); } } /** * Set value object content, according to property values: * for each attribute defined in the v.o. that matches with a row in the grid, its value will be retrieved from the grid and set into the v.o. */ public final void getProperties(ValueObject valueObject) { try { PropertyDescriptor[] props = Introspector.getBeanInfo(valueObject.getClass()).getPropertyDescriptors(); int rowIndex = -1; Object obj = null; for(int i=0;i<props.length;i++) { rowIndex = model.findRow(props[i].getName()); if (rowIndex != -1) { obj = getPropertyValue(rowIndex); try { if (obj!=null && !obj.getClass().equals(props[i].getPropertyType())) { // convert property value, according to property type... if (java.util.Date.class.isAssignableFrom(obj.getClass())) { if (props[i].getPropertyType().equals(java.sql.Date.class)) obj = new java.sql.Date(((java.util.Date)obj).getTime()); else if (props[i].getPropertyType().equals(java.sql.Timestamp.class)) obj = new java.sql.Timestamp(((java.util.Date)obj).getTime()); } else if (Number.class.isAssignableFrom(obj.getClass())) { if (props[i].getPropertyType().equals(Integer.class) || props[i].getPropertyType().equals(Integer.TYPE)) obj = new Integer(((Number)obj).intValue()); else if (props[i].getPropertyType().equals(Long.class) || props[i].getPropertyType().equals(Long.TYPE)) obj = new Long(((Number)obj).longValue()); else if (props[i].getPropertyType().equals(Short.class) || props[i].getPropertyType().equals(Short.TYPE)) obj = new Short(((Number)obj).shortValue()); else if (props[i].getPropertyType().equals(Float.class) || props[i].getPropertyType().equals(Float.TYPE)) obj = new Float(((Number)obj).floatValue()); else if (props[i].getPropertyType().equals(Double.class) || props[i].getPropertyType().equals(Double.TYPE)) obj = new Double(((Number)obj).doubleValue()); else if (props[i].getPropertyType().equals(java.math.BigDecimal.class)) obj = new java.math.BigDecimal(((Number)obj).doubleValue()); } } props[i].getWriteMethod().invoke(valueObject,new Object[] {obj}); } catch (IllegalArgumentException ex1) { } } } } catch (Exception ex) { Logger.error(this.getClass().getName(), "getProperties", ex.getMessage(), ex); } } /** * @param rowIndex row index * @return attribute name, related to the specified row index */ public final String getAttributeName(int rowIndex) { return model.getAttributeName(rowIndex); } /** * @return insert button linked to grid */ public InsertButton getInsertButton() { return insertButton; } /** * @return edit button linked to grid */ public EditButton getEditButton() { return editButton; } /** * Set edit button linked to grid. * @param editButton edit button linked to grid */ public void setEditButton(EditButton editButton) { if (this.editButton != null) this.editButton.removeDataController(this); this.editButton = editButton; if (editButton != null) editButton.addDataController(this); bindedButtons.add(editButton); } /** * Set insert button linked to grid. * @param insertButton insert button linked to grid */ public void setInsertButton(InsertButton insertButton) { if (this.insertButton != null) this.insertButton.removeDataController(this); this.insertButton = insertButton; if (insertButton != null) insertButton.addDataController(this); bindedButtons.add(insertButton); } /** * @return reload button linked to grid */ public ReloadButton getReloadButton() { return reloadButton; } /** * Set reload button linked to grid. * @param reloadButton reload button linked to grid */ public void setReloadButton(ReloadButton reloadButton) { if (this.reloadButton != null) this.reloadButton.removeDataController(this); this.reloadButton = reloadButton; if (reloadButton != null) reloadButton.addDataController(this); bindedButtons.add(reloadButton); } /** * @return save button linked to grid. */ public SaveButton getSaveButton() { return saveButton; } /** * Set save button linked to grid. * @param saveButton save button linked to grid */ public void setSaveButton(SaveButton saveButton) { if (this.saveButton != null) this.saveButton.removeDataController(this); this.saveButton = saveButton; if (saveButton != null) { //imposta listener per nuovo pulsante saveButton.addDataController(this); saveButton.setEnabled(false); bindedButtons.add(saveButton); } } /** * reload */ public void reload() { int row = grid.getEditingRow(); int col = grid.getEditingColumn(); if (row>=0 && col>=0) grid.getCellEditor(row, col).stopCellEditing(); if (getMode()!=Consts.READONLY) { // view confirmation dialog... if (JOptionPane.showConfirmDialog(this, ClientSettings.getInstance().getResources().getResource("Cancel changes and reload data?"), ClientSettings.getInstance().getResources().getResource("Attention"), JOptionPane.YES_NO_OPTION)==JOptionPane.YES_OPTION) { setMode(Consts.READONLY); controller.loadData(this); grid.repaint(); if (getInsertButton()!=null) getInsertButton().setEnabled(true); if (getEditButton()!=null) getEditButton().setEnabled(true); if (getReloadButton()!=null) getReloadButton().setEnabled(true); if (getSaveButton()!=null) getSaveButton().setEnabled(false); grid.setRowSelectionInterval(0,0); grid.setColumnSelectionInterval(0,0); } } else if (getMode()==Consts.READONLY) { setMode(Consts.READONLY); controller.loadData(this); grid.repaint(); if (getInsertButton()!=null) getInsertButton().setEnabled(true); if (getEditButton()!=null) getEditButton().setEnabled(true); if (getReloadButton()!=null) getReloadButton().setEnabled(true); if (getSaveButton()!=null) getSaveButton().setEnabled(false); grid.setRowSelectionInterval(0,0); grid.setColumnSelectionInterval(0,0); } } /** * insert */ public void insert() { if (getMode()==Consts.READONLY) { setMode(Consts.INSERT); if (getInsertButton()!=null) getInsertButton().setEnabled(false); if (getEditButton()!=null) getEditButton().setEnabled(false); if (getReloadButton()!=null) getReloadButton().setEnabled(true); if (getSaveButton()!=null) getSaveButton().setEnabled(true); grid.setRowSelectionInterval(0,0); grid.setColumnSelectionInterval(0,0); grid.editCellAt(grid.getSelectedRow(),grid.getSelectedColumn()); } else Logger.error(this.getClass().getName(),"insert","Setting grid to insert mode is not allowed: grid must be in read only mode.",null); } /** * copy */ public void copy() { } /** * edit */ public void edit() { if (getMode()==Consts.READONLY) { setMode(Consts.EDIT); if (getInsertButton()!=null) getInsertButton().setEnabled(false); if (getEditButton()!=null) getEditButton().setEnabled(false); if (getReloadButton()!=null) getReloadButton().setEnabled(true); if (getSaveButton()!=null) getSaveButton().setEnabled(true); if (grid.getSelectedRow()==-1) { grid.setRowSelectionInterval(0, 0); } grid.setColumnSelectionInterval(0,0); grid.requestFocus(); int col = 0; grid.editCellAt(grid.getSelectedRow(),grid.getSelectedColumn()); } else Logger.error(this.getClass().getName(),"edit","Setting grid to edit mode is not allowed: grid must be in read only mode.",null); } /** * delete */ public void delete() { } /** * Execute a validation on changed rows. * @return <code>true</code> if all changed rows are in a valid state, <code>false</code> otherwise */ public final boolean validateRows() { try { if (getMode()!=Consts.READONLY) { // validate pending changes... int row = grid.getEditingRow(); int col = grid.getEditingColumn(); if ((row>=0) && (col>=0) && !grid.getCellEditor(row, col).stopCellEditing()) // pending invalid changes found... return false; int[] rows = new int[0]; if (getMode() == Consts.EDIT) rows = model.getChangedRowNumbers(); // all changed rows are validated... for (int i=0; i<rows.length; i++) { if (model.isRequired(i) && model.getValueAt(i,1)==null) { JOptionPane.showMessageDialog(this, ClientSettings.getInstance().getResources().getResource("A mandatory column is empty."), ClientSettings.getInstance().getResources().getResource("Value not valid"), JOptionPane.ERROR_MESSAGE); grid.editCellAt(i, 1); grid.requestFocus(); return false; } } return(true); } else // validation skipped because grid is in read only mode... return true; } catch (Throwable ex) { Logger.error(this.getClass().getName(), "validateRows", "Error on validating columns.", ex); return(false); } } /** * Method called when user clicks on save button. * @return <code>true</code> if saving operation was correctly completed, <code>false</code> otherwise */ public final boolean save() { int previousMode; // current grid mode... boolean response = false; if (getMode()!=Consts.READONLY) { try { // all columns are validated: if a validation error occours then saving is interrupted... if (!validateRows()) return false; } catch (Exception ex) { Logger.error(this.getClass().getName(), "save", "Error on grid validation.", ex); } try { // call grid controller to save data... previousMode = getMode(); if (getMode()==Consts.INSERT) { response = controller.insertRecords(this,model.getChangedRowNumbers()); } else if (getMode()==Consts.EDIT) { response = controller.updateRecords(this,model.getChangedRowNumbers()); } if (response) { try { // patch inserted to disable image cell editor... int row = grid.getEditingRow(); int col = grid.getEditingColumn(); } catch (Exception ex1) { } model.setMode(Consts.READONLY); if (getReloadButton()!=null) getReloadButton().setEnabled(true); if (getSaveButton()!=null) getSaveButton().setEnabled(false); // reset toolbar buttons state... if (getEditButton()!=null) getEditButton().setEnabled(true); if (getInsertButton()!=null) getInsertButton().setEnabled(true); } else { // saving operation throws an error: it will be viewed on a dialog... JOptionPane.showMessageDialog( this, ClientSettings.getInstance().getResources().getResource("Error while saving"), ClientSettings.getInstance().getResources().getResource("Saving Error"), JOptionPane.ERROR_MESSAGE ); } } catch (Exception ex) { Logger.error(this.getClass().getName(), "save", "Error while saving.", ex); JOptionPane.showMessageDialog( this, ClientSettings.getInstance().getResources().getResource("Error while saving")+":\n"+ex.getMessage(), ClientSettings.getInstance().getResources().getResource("Saving Error"), JOptionPane.ERROR_MESSAGE ); } } else Logger.error(this.getClass().getName(),"save","Saving data is not allowed in read only mode.",null); return response; } /** * export */ public void export() { } /** * import */ public void importData() { } /** * getFunctionId * * @return String */ public String getFunctionId() { return ""; } /** * isButtonDisabled * * @param button GenericButton * @return boolean */ public boolean isButtonDisabled(GenericButton button) { return false; } /** * filterSort */ public void filterSort() { } /** * @return controller used to fetch data (property values) */ public final PropertyGridController getController() { return controller; } /** * Set the controller used to fetch data (property values). * @param controller controller used to fetch data (property values) */ public final void setController(PropertyGridController controller) { this.controller = controller; } public final void setPropertyNameWidth(int width) { grid.getColumnModel().getColumn(0).setPreferredWidth(width); } public final void setPropertyValueWidth(int width) { grid.getColumnModel().getColumn(1).setPreferredWidth(width); } public final int getPropertyNameWidth(int width) { return grid.getColumnModel().getColumn(0).getPreferredWidth(); } public final int getPropertyValueWidth() { return grid.getColumnModel().getColumn(1).getPreferredWidth(); } /** * @return collection of buttons binded to grid (InsertButton, EditButton, etc) */ public final HashSet getBindedButtons() { return bindedButtons; } }