001package jmri.jmrit.roster; 002 003import java.awt.BorderLayout; 004import java.awt.Color; 005import java.awt.Dimension; 006import java.awt.GridBagConstraints; 007import java.awt.GridBagLayout; 008import java.awt.Insets; 009import java.util.Vector; 010 011import javax.swing.BorderFactory; 012import javax.swing.JLabel; 013import javax.swing.JPanel; 014import javax.swing.JScrollPane; 015import javax.swing.JTable; 016import javax.swing.JTextField; 017import javax.swing.table.AbstractTableModel; 018import javax.swing.table.TableCellEditor; 019import javax.swing.table.TableCellRenderer; 020 021import jmri.InstanceManager; 022import jmri.util.gui.GuiLafPreferencesManager; 023import jmri.util.swing.EditableResizableImagePanel; 024import jmri.util.swing.MultiLineCellRenderer; 025import jmri.util.swing.MultiLineCellEditor; 026import jmri.util.swing.ResizableRowDataModel; 027 028/** 029 * A media pane for roster configuration tool. It contains:<ul> 030 * <li>a selector for roster image (a large image for throttle 031 * background...)</li> 032 * <li>a selector for roster icon (a small image for list displays...)</li> 033 * <li>a selector for roster URL (link to wikipedia page about 034 * prototype...)</li> 035 * <li>a table displaying user attributes for that locomotive</li> 036 * </ul> 037 * <hr> 038 * This file is part of JMRI. 039 * <p> 040 * JMRI is free software; you can redistribute it and/or modify it under the 041 * terms of version 2 of the GNU General Public License as published by the Free 042 * Software Foundation. See the "COPYING" file for a copy of this license. 043 * <p> 044 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY 045 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 046 * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 047 * 048 * @author Lionel Jeanson Copyright (C) 2009 049 * @author Randall Wood Copyright (C) 2014 050 */ 051public class RosterMediaPane extends JPanel { 052 053 JLabel _imageFPlabel = new JLabel(); 054 EditableResizableImagePanel _imageFilePath; 055 JLabel _iconFPlabel = new JLabel(); 056 EditableResizableImagePanel _iconFilePath; 057 JLabel _URLlabel = new JLabel(); 058 JTextField _URL = new JTextField(30); 059 RosterAttributesTableModel rosterAttributesModel; 060 061 /** 062 * This constructor allows the panel to be used in visual bean editors, but 063 * should not be used in code. 064 */ 065 public RosterMediaPane() { 066 super(); 067 } 068 069 public RosterMediaPane(RosterEntry r) { 070 super(); 071 _imageFilePath = new EditableResizableImagePanel(r.getImagePath(), 320, 240); 072 _imageFilePath.setDropFolder(Roster.getDefault().getRosterFilesLocation()); 073 _imageFilePath.setToolTipText(Bundle.getMessage("MediaRosterImageToolTip")); 074 _imageFilePath.setBorder(BorderFactory.createLineBorder(Color.blue)); 075 _imageFPlabel.setText(Bundle.getMessage("MediaRosterImageLabel")); 076 077 _iconFilePath = new EditableResizableImagePanel(r.getIconPath(), 160, 120); 078 _iconFilePath.setDropFolder(Roster.getDefault().getRosterFilesLocation()); 079 _iconFilePath.setToolTipText(Bundle.getMessage("MediaRosterIconToolTip")); 080 _iconFilePath.setBorder(BorderFactory.createLineBorder(Color.blue)); 081 _iconFPlabel.setText(Bundle.getMessage("MediaRosterIconLabel")); 082 083 _URL.setText(r.getURL()); 084 _URL.setToolTipText(Bundle.getMessage("MediaRosterURLToolTip")); 085 _URLlabel.setText(Bundle.getMessage("MediaRosterURLLabel")); 086 087 rosterAttributesModel = new RosterAttributesTableModel(r); //t, columnNames); 088 JTable jtAttributes = new JTable(){ 089 // MultiLineRenderer and MultiLineCellEditor for value column 090 @Override 091 public TableCellRenderer getCellRenderer(int row, int column) { 092 // no remapping of model vs view columns 093 if (column == 1) { 094 return new MultiLineCellRenderer(); 095 } 096 return super.getCellRenderer(row, column); 097 } 098 @Override 099 public TableCellEditor getCellEditor(int row, int column) { 100 // no remapping of model vs view columns 101 if (column == 1) { 102 return new MultiLineCellEditor(); 103 } 104 return super.getCellEditor(row, column); 105 } 106 }; 107 rosterAttributesModel.associatedTable = jtAttributes; 108 jtAttributes.setModel(rosterAttributesModel); 109 JScrollPane jsp = new JScrollPane(jtAttributes); 110 jtAttributes.setFillsViewportHeight(true); 111 112 JPanel mediap = new JPanel(); 113 GridBagLayout gbLayout = new GridBagLayout(); 114 GridBagConstraints gbc = new GridBagConstraints(); 115 Dimension textFieldDim = new Dimension(320, 20); 116 Dimension imageFieldDim = new Dimension(320, 200); 117 Dimension iconFieldDim = new Dimension(160, 100); 118 Dimension tableDim = new Dimension(400, 200); 119 mediap.setLayout(gbLayout); 120 121 gbc.insets = new Insets(0, 8, 0, 8); 122 gbc.gridx = 0; 123 gbc.gridy = 0; 124 gbLayout.setConstraints(_imageFPlabel, gbc); 125 mediap.add(_imageFPlabel); 126 127 gbc.gridx = 1; 128 gbc.gridy = 0; 129 _imageFilePath.setMinimumSize(imageFieldDim); 130 _imageFilePath.setMaximumSize(imageFieldDim); 131 _imageFilePath.setPreferredSize(imageFieldDim); 132 gbLayout.setConstraints(_imageFilePath, gbc); 133 mediap.add(_imageFilePath); 134 135 gbc.gridx = 0; 136 gbc.gridy = 2; 137 gbLayout.setConstraints(_iconFPlabel, gbc); 138 mediap.add(_iconFPlabel); 139 140 gbc.gridx = 1; 141 gbc.gridy = 2; 142 _iconFilePath.setMinimumSize(iconFieldDim); 143 _iconFilePath.setMaximumSize(iconFieldDim); 144 _iconFilePath.setPreferredSize(iconFieldDim); 145 gbLayout.setConstraints(_iconFilePath, gbc); 146 mediap.add(_iconFilePath); 147 148 gbc.gridx = 0; 149 gbc.gridy = 4; 150 gbLayout.setConstraints(_URLlabel, gbc); 151 mediap.add(_URLlabel); 152 153 gbc.gridx = 1; 154 gbc.gridy = 4; 155 _URL.setMinimumSize(textFieldDim); 156 _URL.setPreferredSize(textFieldDim); 157 gbLayout.setConstraints(_URL, gbc); 158 mediap.add(_URL); 159 160 this.setLayout(new BorderLayout()); 161 add(mediap, BorderLayout.NORTH); 162 add(new JLabel(Bundle.getMessage("MediaRosterAttributeTableDescription")), BorderLayout.CENTER); // some nothing in the middle 163 jsp.setMinimumSize(tableDim); 164 jsp.setMaximumSize(tableDim); 165 jsp.setPreferredSize(tableDim); 166 add(jsp, BorderLayout.SOUTH); 167 } 168 169 public boolean guiChanged(RosterEntry r) { 170 if (!r.getURL().equals(_URL.getText())) { 171 return true; 172 } 173 if ((r.getImagePath() != null && !r.getImagePath().equals(_imageFilePath.getImagePath())) 174 || (r.getImagePath() == null && _imageFilePath.getImagePath() != null)) { 175 return true; 176 } 177 if ((r.getIconPath() != null && !r.getIconPath().equals(_iconFilePath.getImagePath())) 178 || (r.getIconPath() == null && _iconFilePath.getImagePath() != null)) { 179 return true; 180 } 181 return rosterAttributesModel.wasModified(); 182 } 183 184 public void update(RosterEntry r) { 185 r.setURL(_URL.getText()); 186 r.setImagePath(_imageFilePath.getImagePath()); 187 r.setIconPath(_iconFilePath.getImagePath()); 188 rosterAttributesModel.updateModel(r); 189 } 190 191 public void dispose() { 192 if (log.isDebugEnabled()) { 193 log.debug("dispose"); 194 } 195 if (_imageFilePath != null) { 196 _imageFilePath.removeDnd(); 197 } 198 if (_iconFilePath != null) { 199 _iconFilePath.removeDnd(); 200 } 201 } 202 203 private static class RosterAttributesTableModel extends AbstractTableModel implements ResizableRowDataModel { 204 205 Vector<KeyValueModel> attributes; 206 String[] titles; 207 boolean wasModified; 208 JTable associatedTable; 209 210 private static class KeyValueModel { 211 212 public KeyValueModel(String k, String v) { 213 key = k; 214 value = v; 215 } 216 public String key, value; 217 } 218 219 public RosterAttributesTableModel(RosterEntry r) { 220 setModel(r); 221 222 titles = new String[2]; 223 titles[0] = Bundle.getMessage("MediaRosterAttributeName"); 224 titles[1] = Bundle.getMessage("MediaRosterAttributeValue"); 225 } 226 227 public void setModel(RosterEntry r) { 228 attributes = new Vector<>(r.getAttributes().size()); 229 for (String key : r.getAttributes()) { 230 attributes.add(new KeyValueModel(key, r.getAttribute(key))); 231 } 232 wasModified = false; 233 } 234 235 public void updateModel(RosterEntry r) { 236 for (KeyValueModel kv : attributes) { 237 if ((kv.key.length() > 0) && // only update if key value defined, will do the remove to 238 ((r.getAttribute(kv.key) == null) || (kv.value.compareTo(r.getAttribute(kv.key)) != 0))) { 239 r.putAttribute(kv.key, kv.value); 240 } 241 } 242 //remove undefined keys 243 // not very efficient algorithm! 244 r.getAttributes().removeIf(s -> !keyExist(s)); 245 wasModified = false; 246 } 247 248 private boolean keyExist(String k) { 249 if (k == null) { 250 return false; 251 } 252 for (KeyValueModel attribute : attributes) { 253 if (k.compareTo(attribute.key) == 0) { 254 return true; 255 } 256 } 257 return false; 258 } 259 260 @Override 261 public int getColumnCount() { 262 return 2; 263 } 264 265 @Override 266 public int getRowCount() { 267 return attributes.size() + 1; 268 } 269 270 @Override 271 public String getColumnName(int col) { 272 return titles[col]; 273 } 274 275 @Override 276 public Object getValueAt(int row, int col) { 277 if (row < attributes.size()) { 278 if (col == 0) { 279 return attributes.get(row).key; 280 } 281 if (col == 1) { 282 String content = attributes.get(row).value; 283 int lines = content.split("\n").length; 284 resizeRowToText(row, lines); 285 return content; 286 } 287 } 288 return "..."; 289 } 290 291 @Override 292 public void resizeRowToText(int modelRow, int heightInLines) { 293 int height = heightInLines * (InstanceManager.getDefault(GuiLafPreferencesManager.class).getFontSize() + 4); // same line height as in RosterTable 294 if (height != associatedTable.getRowHeight(modelRow)) { 295 associatedTable.setRowHeight(modelRow, height); 296 } 297 } 298 299 @Override 300 public void setValueAt(Object value, int row, int col) { 301 KeyValueModel kv; 302 303 if (row < attributes.size()) // already exist? 304 { 305 kv = attributes.get(row); 306 } else { 307 kv = new KeyValueModel("", ""); 308 } 309 310 if (col == 0) // update key 311 //Force keys to be save as a single string with no spaces 312 { 313 if (!keyExist(((String) value).replaceAll("\\s", ""))) // if not exist 314 { 315 kv.key = ((String) value).replaceAll("\\s", ""); 316 } else { 317 setValueAt(value + "-1", row, col); // else change key name 318 return; 319 } 320 } 321 322 if (col == 1) // update value 323 { 324 kv.value = (String) value; 325 } 326 if (row < attributes.size()) // existing one 327 { 328 attributes.set(row, kv); 329 } else { 330 attributes.add(row, kv); // new one 331 } 332 if ((col == 0) && (kv.key.compareTo("") == 0)) { 333 attributes.remove(row); // actually maybe remove 334 } 335 wasModified = true; 336 fireTableCellUpdated(row, col); 337 } 338 339 @Override 340 public boolean isCellEditable(int row, int col) { 341 return true; 342 } 343 344 public boolean wasModified() { 345 return wasModified; 346 } 347 } 348 349 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(RosterMediaPane.class); 350}