| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| |
| package org.netbeans.modules.properties; |
| |
| |
| import java.io.Serializable; |
| import javax.swing.event.TableModelEvent; |
| import javax.swing.table.AbstractTableModel; |
| import javax.swing.table.TableColumn; |
| import javax.swing.JTable; |
| |
| import org.openide.util.NbBundle; |
| import org.openide.util.WeakListeners; |
| |
| |
| /** |
| * Model for the properties edit table. First column represents keys containing all .properties |
| * files belonging to bundle represnted by this model. Each next columns represents values |
| * for on .properties file from that bundle. |
| * |
| * @author Petr Jiricka |
| * @see BundleEditPanel |
| * @see javax.swing.table.AbstractTableModel |
| */ |
| public class PropertiesTableModel extends AbstractTableModel { |
| |
| /** Generated serialized version UID. */ |
| static final long serialVersionUID = -7882925922830244768L; |
| |
| /** <code>PropertiesDataObject</code> this table presents. */ |
| private BundleStructure structure; |
| |
| /** Listens to changes on the bundle structure. */ |
| private PropertyBundleListener bundleListener; |
| |
| /** Create a data node for a given data object. |
| * The provided children object will be used to hold all child nodes. |
| * @param structure model to work with |
| */ |
| public PropertiesTableModel(BundleStructure structure) { |
| super(); |
| this.structure = structure; |
| |
| // listener for the BundleStructure |
| bundleListener = new TablePropertyBundleListener(); |
| |
| structure.addPropertyBundleListener( |
| (PropertyBundleListener) WeakListeners.create(PropertyBundleListener.class, bundleListener, structure) |
| ); |
| |
| } |
| |
| /** Gets the class for a model. Overrides column class. */ |
| @Override |
| public Class getColumnClass(int columnIndex) { |
| return StringPair.class; |
| } |
| |
| /** Gets the number of rows in the model. Implements superclass abstract method. */ |
| public int getRowCount() { |
| return structure.getKeyCount(); |
| } |
| |
| /** Gets the number of columns in the model. Implements superclass abstract method. */ |
| public int getColumnCount() { |
| return structure.getEntryCount() + 1; |
| } |
| |
| /** Gets the value for the given row and column. Implements superclass abstract method. */ |
| public Object getValueAt(int row, int column) { |
| BundleStructure bs = structure; |
| |
| if(column == 0) |
| // Get StringPair for key. |
| return stringPairForKey(row);//bs.keyAt(row); |
| else { |
| // Get StringPair for value. |
| Element.ItemElem item; |
| try { |
| item = bs.getItem(column - 1, bs.keyAt(row)); |
| } catch (ArrayIndexOutOfBoundsException aie) { |
| item = null; |
| } |
| return stringPairForValue(item); |
| } |
| } |
| |
| /** Gets string pair for a key in an item (may be null). */ |
| private StringPair stringPairForKey(int row) { |
| BundleStructure bs = structure; |
| Element.ItemElem item = bs.getItem(0, bs.keyAt(row)); |
| StringPair sp; |
| if (item == null) |
| sp = new StringPair("", bs.keyAt(row), true); // NOI18N |
| else |
| sp = new StringPair(item.getComment(), bs.keyAt(row), true); |
| |
| if (structure.getEntryCount() > 1) |
| sp.setCommentEditable(false); |
| |
| return sp; |
| } |
| |
| /** Gets string pair for a value in an item (may be null). */ |
| private StringPair stringPairForValue(Element.ItemElem item) { |
| if (item == null) |
| // item doesnt't exist -> value is null |
| return new StringPair(null, null); |
| else |
| return new StringPair(item.getComment(), item.getValue()); |
| } |
| |
| /** Gets name for column. Overrides superclass method. |
| * @param column model index of column |
| * @return name for column */ |
| @Override |
| public String getColumnName(int column) { |
| String leading; |
| |
| // Construct label. |
| if(column == structure.getSortIndex()) |
| // Place for drawing ascending/descending mark in renderer. |
| leading = " "; // NOI18N |
| else |
| leading = " "; // NOI18N |
| |
| if(column == 0) |
| return leading+NbBundle.getBundle(PropertiesTableModel.class).getString("LAB_KeyColumnLabel"); |
| else { |
| if(structure.getEntryCount() == 1) |
| return leading+NbBundle.getBundle(PropertiesTableModel.class).getString("LBL_ColumnValue"); |
| else { |
| PropertiesFileEntry entry = structure.getNthEntry(column - 1); |
| return entry == null ? "" : leading+Util.getLocaleLabel(entry); // NOI18N |
| } |
| } |
| } |
| |
| /** Sets the value at rowIndex and columnIndex. Overrides superclass method. */ |
| @Override |
| public void setValueAt(Object aValue, int rowIndex, int columnIndex) { |
| // If values equals -> no change was made -> return immediatelly. |
| if (aValue.equals(getValueAt(rowIndex, columnIndex))) { |
| return; |
| } |
| |
| // PENDING - set comment for all files |
| // Is key. |
| if (columnIndex == 0) { |
| BundleStructure bs = structure; |
| String oldValue = (String)bs.keyAt(rowIndex); |
| if (oldValue == null) { |
| return; |
| } |
| String newValue = ((StringPair)aValue).getValue(); |
| if (newValue == null) { // Key can be an empty string |
| // Remove from all files. |
| return; |
| } else { |
| // Set in all files |
| for (int i=0; i < structure.getEntryCount(); i++) { |
| PropertiesFileEntry entry = structure.getNthEntry(i); |
| if (entry != null) { |
| PropertiesStructure ps = entry.getHandler().getStructure(); |
| if (ps != null) { |
| // set the key |
| if (!oldValue.equals(newValue)) { |
| ps.renameItem(oldValue, newValue); |
| // this resorting is necessary only if this column index is same as |
| // column according the sort is performed, REFINE |
| structure.sort(-1); |
| } |
| // set the comment |
| if (i == 0) { |
| Element.ItemElem item = ps.getItem(newValue); |
| if (item != null && ((StringPair)aValue).isCommentEditable()) { |
| // only set if they differ |
| if (!item.getComment().equals(((StringPair)aValue).getComment())) |
| item.setComment(((StringPair)aValue).getComment()); |
| } |
| } |
| } |
| } |
| } |
| } |
| } else { |
| // Property value. |
| PropertiesFileEntry entry = structure.getNthEntry(columnIndex - 1); |
| String key = structure.keyAt(rowIndex); |
| if (entry != null && key != null) { |
| PropertiesStructure ps = entry.getHandler().getStructure(); |
| if (ps != null) { |
| Element.ItemElem item = ps.getItem(key); |
| if (item != null) { |
| item.setValue(((StringPair)aValue).getValue()); |
| item.setComment(((StringPair)aValue).getComment()); |
| // this resorting is necessary only if this column index is same as |
| // column according the sort is performed, REFINE |
| structure.sort(-1); |
| } else { |
| if ((((StringPair)aValue).getValue().length() > 0) || (((StringPair)aValue).getComment().length() > 0)) { |
| ps.addItem(key, ((StringPair)aValue).getValue(), ((StringPair)aValue).getComment()); |
| // this resorting is necessary only if this column index is same as |
| // column according the sort is performed, REFINE |
| structure.sort(-1); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| /** Overrides superclass method. Overrides superclass method. |
| * @return true for all cells */ |
| @Override |
| public boolean isCellEditable(int rowIndex, int columnIndex) { |
| if (columnIndex == 0) { |
| return !structure.isReadOnly(); |
| } else { |
| PropertiesFileEntry entry = structure.getNthEntry(columnIndex-1); |
| if (entry != null) |
| return entry.getFile().canWrite(); |
| return false; |
| } |
| } |
| |
| /** Fires a TableModelEvent - change of one column */ |
| public void fireTableColumnChanged(int index) { |
| int columnModelIndex = index; |
| |
| // reset the header value as well |
| Object list[] = listenerList.getListenerList(); |
| for (int i = 0; i < list.length; i++) { |
| if (list[i] instanceof JTable) { |
| JTable jt = (JTable)list[i]; |
| try { |
| TableColumn column = jt.getColumnModel().getColumn(index); |
| columnModelIndex = column.getModelIndex(); |
| column.setHeaderValue(jt.getModel().getColumnName(columnModelIndex)); |
| } catch (ArrayIndexOutOfBoundsException abe) { |
| // only catch exception |
| } |
| jt.getTableHeader().repaint(); |
| } |
| } |
| fireTableChanged(new TableModelEvent(this, 0, getRowCount() - 1, columnModelIndex)); |
| } |
| |
| /** |
| * Get FileEntry according to column in Table View |
| * @param column |
| * @return |
| */ |
| PropertiesFileEntry getFileEntry(int column) { |
| return structure.getNthEntry(column-1); |
| } |
| /** Overrides superclass method. */ |
| @Override |
| public String toString() { |
| StringBuffer result = new StringBuffer(); |
| result.append("------------------------------ TABLE MODEL DUMP -----------------------\n"); // NOI18N |
| for (int row = 0; row < getRowCount(); row ++) { |
| for (int column = 0; column < getColumnCount(); column ++) { |
| StringPair sp = (StringPair)getValueAt(row, column); |
| result.append("[" /*+ sp.getComment() + "," */+ sp.getValue() + "]"); // NOI18N |
| if (column == 0) |
| result.append(" : "); // NOI18N |
| else |
| if (column == getColumnCount() - 1) |
| result.append("\n"); // NOI18N |
| else |
| result.append(","); // NOI18N |
| } |
| } |
| result.append("---------------------------- END TABLE MODEL DUMP ---------------------\n"); // NOI18N |
| return result.toString(); |
| } |
| |
| /** Cancels editing in all listening JTables if appropriate. */ |
| private void cancelEditingInTables(CancelSelector can) { |
| |
| Object list[] = listenerList.getListenerList(); |
| for(int i = 0; i < list.length; i++) { |
| if(list[i] instanceof BundleEditPanel.BundleTable) { |
| BundleEditPanel.BundleTable jt = (BundleEditPanel.BundleTable)list[i]; |
| if (can.doCancelEditing(jt.getEditingRow(), jt.getEditingColumn())) { |
| jt.removeEditorSilent(); |
| } |
| } |
| } |
| } |
| |
| /** Gets <code>CancelSelector</code> for this table. */ |
| private CancelSelector getDefaultCancelSelector() { |
| return new CancelSelector() { |
| /** Returns whether editing should be canceled for given row and column. */ |
| public boolean doCancelEditing(int row, int column) { |
| return (row >= 0 && row < getRowCount() && column >= 0 && column < getColumnCount()); |
| } |
| }; |
| } |
| |
| |
| /** Interface which finds out whether editing should be canceled if given cell is edited. */ |
| private static interface CancelSelector { |
| /** Returns whether editing should be canceled for given row and column. */ |
| public boolean doCancelEditing(int row, int column); |
| } // End of interface CancelSelector. |
| |
| |
| /** Inner class. Listener for changes on bundle structure. */ |
| private class TablePropertyBundleListener implements PropertyBundleListener { |
| public void bundleChanged(final PropertyBundleEvent evt) { |
| // quick patch for bug #13026 |
| // (ensure visual updates are performed in AWT thread) |
| if (java.awt.EventQueue.isDispatchThread()) { |
| doBundleChanged(evt); |
| } |
| else { |
| java.awt.EventQueue.invokeLater(new Runnable() { |
| public void run() { |
| doBundleChanged(evt); |
| } |
| }); |
| } |
| } |
| |
| private void doBundleChanged(PropertyBundleEvent evt) { |
| int changeType = evt.getChangeType(); |
| |
| if(changeType == PropertyBundleEvent.CHANGE_STRUCT) { |
| // Structure changed. |
| |
| // Note: Normal way would be use the next commented out rows, which should do in effect |
| // the same thing like reseting the model, but it doesn't, therefore we reset the model directly. |
| |
| cancelEditingInTables(getDefaultCancelSelector()); |
| fireTableStructureChanged(); |
| |
| Object[] list = PropertiesTableModel.super.listenerList.getListenerList(); |
| for(int i = 0; i < list.length; i++) { |
| if(list[i] instanceof JTable) { |
| //!!! strange comment this model should be only stateless proxy |
| // Its necessary to create new instance of model otherwise the 'old' model values would remain. |
| ((JTable)list[i]).setModel(new PropertiesTableModel(PropertiesTableModel.this.structure)); |
| } |
| } |
| } else if(changeType == PropertyBundleEvent.CHANGE_ALL) { |
| // All items changed (keyset). |
| cancelEditingInTables(getDefaultCancelSelector()); |
| |
| // reset all header values as well |
| Object[] list = PropertiesTableModel.super.listenerList.getListenerList(); |
| for (int i = 0; i < list.length; i++) { |
| if (list[i] instanceof JTable) { |
| JTable jt = (JTable)list[i]; |
| |
| for (int j=0 ; j < jt.getColumnModel().getColumnCount(); j++) { |
| TableColumn column = jt.getColumnModel().getColumn(j); |
| // column.setHeaderValue(jt.getModel().getColumnName(column.getModelIndex())); |
| column.setHeaderValue(jt.getModel().getColumnName(j)); |
| } |
| } |
| } |
| |
| fireTableDataChanged(); |
| } else if(changeType == PropertyBundleEvent.CHANGE_FILE) { |
| // File changed. |
| final int index = structure.getEntryIndexByFileName(evt.getEntryName()); |
| //This mean file deleted |
| if (index == -1) { |
| fireTableStructureChanged(); |
| // if (Boolean.getBoolean("netbeans.debug.exceptions")) // NOI18N |
| // (new Exception("Changed file not found")).printStackTrace(); // NOI18N |
| return; |
| } |
| |
| cancelEditingInTables(new CancelSelector() { |
| public boolean doCancelEditing(int row, int column) { |
| if (!(row >= 0 && row < getRowCount() && column >= 0 && column < getColumnCount())) |
| return false; |
| return (column == index + 1); |
| } |
| }); |
| // fireTableColumnChanged(index + 1); |
| fireTableStructureChanged(); |
| } else if(changeType == PropertyBundleEvent.CHANGE_ITEM) { |
| // one item changed |
| final int index2 = structure.getEntryIndexByFileName(evt.getEntryName()); |
| final int keyIndex = structure.getKeyIndexByName(evt.getItemName()); |
| |
| if(index2 == -1 || keyIndex == -1) { |
| if(Boolean.getBoolean("netbeans.debug.exceptions")) // NOI18N |
| (new Exception("Changed file not found")).printStackTrace(); // NOI18N |
| |
| return; |
| } |
| |
| cancelEditingInTables(new CancelSelector() { |
| public boolean doCancelEditing(int row, int column) { |
| if (!(row >= 0 && row < getRowCount() && column >= 0 && column < getColumnCount())) |
| return false; |
| return (column == index2 + 1 && row == keyIndex); |
| } |
| }); |
| |
| fireTableCellUpdated(keyIndex, index2 + 1); |
| } |
| } |
| } // End of inner class TablePropertyBundleListener. |
| |
| |
| /** |
| * Object for the value for one cell in a table view. |
| * It is used to represent either (comment, value) pair of an item, or a key for an item. |
| */ |
| static class StringPair implements Serializable { |
| |
| /** Holds comment for this instance. */ |
| private String comment; |
| |
| /** Key or value string depending on the <code>keyType</code>. */ |
| private String value; |
| |
| /** Type of instance. */ |
| private boolean keyType; |
| |
| /** Flag if comment is editable for this instance. */ |
| private boolean commentEditable; |
| |
| /** Generated serial version UID. */ |
| static final long serialVersionUID =-463968846283787181L; |
| |
| |
| /** Constructs with empty comment and value. */ |
| public StringPair() { |
| this (null, "", false); // NOI18N |
| } |
| |
| /** Constructs with the given value and no comment. */ |
| public StringPair(String v) { |
| this (null, v, true); |
| } |
| |
| /** Constructs with the given comment and value. */ |
| public StringPair(String c, String v) { |
| this (c, v, false); |
| } |
| |
| /** Constructs with the given comment and value. */ |
| public StringPair(String c, String v, boolean kt) { |
| comment = c; |
| value = v; |
| keyType = kt; |
| commentEditable = true; |
| } |
| |
| |
| /** @return comment associated with this element. */ |
| public String getComment() { |
| return comment; |
| } |
| |
| /** @return the value associated with this element. */ |
| public String getValue() { |
| return value; |
| } |
| |
| /** Overrides superclass method. */ |
| @Override |
| public boolean equals(Object obj) { |
| if(obj == null || !(obj instanceof StringPair)) |
| return false; |
| |
| StringPair compared = (StringPair)obj; |
| |
| // PENDING compare keyTypes as well? |
| |
| // Compare commnents first. |
| if(comment == null && compared.getComment() != null) |
| return false; |
| |
| String str1 = comment; |
| String str2 = compared.getComment(); |
| |
| if(!str1.equals(str2)) |
| return false; |
| |
| // Compare values. |
| if(value == null && compared.getValue() != null) |
| return false; |
| |
| str1 = value; |
| str2 = compared.getValue(); |
| |
| return str1.equals(str2); |
| } |
| |
| @Override |
| public int hashCode() { |
| int hash = 3; |
| hash = 19 * hash + (comment != null ? comment.hashCode() : 0); |
| hash = 19 * hash + (value != null ? value.hashCode() : 0); |
| return hash; |
| } |
| |
| /** Overrides superclass method. */ |
| @Override |
| public String toString() { |
| return value; |
| } |
| |
| /** Returns the type key/value of the pair. */ |
| public boolean isKeyType () { |
| return keyType; |
| } |
| |
| /** @return true if comment should be allowed for editing. */ |
| public boolean isCommentEditable() { |
| return commentEditable; |
| } |
| |
| /** Sets whether the comment should be allowed to be edited. */ |
| public void setCommentEditable(boolean newEditable) { |
| commentEditable = newEditable; |
| } |
| } // End of nested class StringPair. |
| |
| } |