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}