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