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}