// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.roadsigns;
import static org.openstreetmap.josm.tools.I18n.tr;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.XMLDecoder;
import java.beans.XMLEncoder;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JEditorPane;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.Scrollable;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.border.Border;
import javax.swing.border.EtchedBorder;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
import javax.swing.table.AbstractTableModel;
import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.command.ChangePropertyCommand;
import org.openstreetmap.josm.command.Command;
import org.openstreetmap.josm.command.SequenceCommand;
import org.openstreetmap.josm.data.osm.OsmPrimitive;
import org.openstreetmap.josm.gui.ExtendedDialog;
import org.openstreetmap.josm.gui.widgets.MultiSplitLayout.Divider;
import org.openstreetmap.josm.gui.widgets.MultiSplitLayout.Leaf;
import org.openstreetmap.josm.gui.widgets.MultiSplitLayout.Node;
import org.openstreetmap.josm.gui.widgets.MultiSplitLayout.Split;
import org.openstreetmap.josm.gui.widgets.MultiSplitPane;
import org.openstreetmap.josm.plugins.roadsigns.RoadSignsPlugin.PresetMetaData;
import org.openstreetmap.josm.plugins.roadsigns.Sign.SignParameter;
import org.openstreetmap.josm.plugins.roadsigns.Sign.Tag;
import org.openstreetmap.josm.tools.GBC;
import org.openstreetmap.josm.tools.OpenBrowser;
import org.openstreetmap.josm.tools.Pair;
import org.openstreetmap.josm.tools.WindowGeometry;
/**
* Input dialog for road sign.
*
* Allows selection of a combination of road signs.
* It tries to generate the corresponding tags for the object.
*
* As tagging schemes are under constant development, the definitions
* need to be updated when needed.
*
* Often there is more than one way to tag a certain situation.
* So easy configuration is needed (TODO).
*
*/
class RoadSignInputDialog extends ExtendedDialog {
protected SignSelection sel;
protected List<Sign> signs;
protected JTable previewTable;
protected JCheckBox addTrafficSignTag;
protected PreviewTableModel previewModel;
protected JPanel pnlSignSelection;
protected JPanel pnlPossibleSigns;
protected JPanel pnlPossibleSupplements;
protected JEditorPane info;
protected JScrollPane scrollInfo;
private MultiSplitPane multiSplitPane;
RoadSignInputDialog() {
super(Main.parent, tr("Road Sign Plugin"), new String[] {tr("OK"), tr("Cancel")}, false /* modal */);
setRememberWindowGeometry(getClass().getName() + ".geometry",
WindowGeometry.centerInWindow(Main.parent, new Dimension(750, 550)));
this.signs = RoadSignsPlugin.signs;
sel = new SignSelection();
setButtonIcons(new String[] {"ok.png", "cancel.png"});
final JTabbedPane tabs = new JTabbedPane();
tabs.add(tr("signs"), buildSignsPanel());
Action updateAction = new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
RoadSignInputDialog.this.signs = RoadSignsPlugin.signs;
sel = new SignSelection();
tabs.setComponentAt(0, buildSignsPanel());
}
};
tabs.add(tr("settings"), new SettingsPanel(false, updateAction));
setContent(tabs, false);
}
@Override
protected void buttonAction(int i, ActionEvent evt) {
if (i == 0) { // OK Button
Collection<OsmPrimitive> selPrim = Main.getLayerManager().getEditDataSet().getSelected();
if (!selPrim.isEmpty()) {
Main.pref.put("plugin.roadsigns.addTrafficSignTag", addTrafficSignTag.isSelected());
Command cmd = createCommand(selPrim);
if (cmd != null) {
Main.main.undoRedo.add(cmd);
}
}
}
super.buttonAction(i, evt);
}
@Override
public void setVisible(boolean visible) {
if (!visible) {
if (multiSplitPane != null) {
Node model = multiSplitPane.getMultiSplitLayout().getModel();
File f = new File(RoadSignsPlugin.pluginDir(), "roadsigns-layout.xml");
try (
XMLEncoder xmlenc = new XMLEncoder(
new BufferedOutputStream(new FileOutputStream(f))
)) {
xmlenc.writeObject(model);
} catch (FileNotFoundException ex) {
Main.warn("unable to write dialog layout: "+ex);
}
}
}
super.setVisible(visible);
}
private Command createCommand(Collection<OsmPrimitive> selPrim) {
List<Command> cmds = new LinkedList<>();
for (int i = 0; i < previewModel.getRowCount(); i++) {
String key = (String) previewModel.getValueAt(i, 0);
String value = (String) previewModel.getValueAt(i, 1);
cmds.add(new ChangePropertyCommand(selPrim, key, value));
}
if (cmds.isEmpty())
return null;
else if (cmds.size() == 1)
return cmds.get(0);
else
return new SequenceCommand(tr("Change Properties"), cmds);
}
private JComponent buildSignsPanel() {
FlowLayout fLayout = new FlowLayout(FlowLayout.LEFT);
fLayout.setAlignOnBaseline(true);
pnlSignSelection = new JPanel();
pnlSignSelection.setLayout(fLayout);
pnlPossibleSigns = new FixedWidthPanel();
pnlPossibleSupplements = new FixedWidthPanel();
fillSigns();
multiSplitPane = new MultiSplitPane();
File f = new File(RoadSignsPlugin.pluginDir(), "roadsigns-layout.xml");
try (XMLDecoder decoder = new XMLDecoder(new BufferedInputStream(new FileInputStream(f)))) {
Node model = (Node) decoder.readObject();
multiSplitPane.getMultiSplitLayout().setModel(model);
multiSplitPane.getMultiSplitLayout().setFloatingDividers(false);
} catch (IOException | ArrayIndexOutOfBoundsException ex) {
// (COLUMN
// (ROW weight=0.3 (LEAF name=upperleft weight=1.0) upperright)
// (ROW weight=0.5 (LEAF name=middleleft weight=0.5) (LEAF name=middleright weight=0.5))
// (LEAF name=bottom weight=0.2))
Split modelRoot = new Split();
modelRoot.setRowLayout(false);
Split row1 = new Split();
row1.setWeight(0.3);
Leaf upperleft = new Leaf("upperleft");
upperleft.setWeight(1.0);
row1.setChildren(Arrays.asList(upperleft, new Divider(), new Leaf("upperright")));
Split row2 = new Split();
row2.setWeight(0.5);
Leaf middleleft = new Leaf("middleleft");
middleleft.setWeight(0.5);
Leaf middleright = new Leaf("middleright");
middleright.setWeight(0.5);
row2.setChildren(Arrays.asList(middleleft, new Divider(), middleright));
Leaf bottom = new Leaf("bottom");
bottom.setWeight(0.2);
modelRoot.setChildren(Arrays.asList(row1, new Divider(), row2, new Divider(), bottom));
multiSplitPane.getMultiSplitLayout().setModel(modelRoot);
}
multiSplitPane.add(new JScrollPane(pnlSignSelection), "upperleft");
multiSplitPane.add(buildPreviewPanel(), "upperright");
JScrollPane scroll1 = new JScrollPane(pnlPossibleSigns,
JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
scroll1.setPreferredSize(new Dimension(10, 10));
multiSplitPane.add(scroll1, "middleleft");
JScrollPane scroll2 = new JScrollPane(pnlPossibleSupplements,
JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
scroll2.setPreferredSize(new Dimension(10, 10));
multiSplitPane.add(scroll2, "middleright");
info = new JEditorPane();
info.setEditable(false);
info.setContentType("text/html");
info.setText(" ");
info.setBackground(this.getBackground());
info.addHyperlinkListener(new HyperlinkListener() {
@Override
public void hyperlinkUpdate(HyperlinkEvent e) {
if (e == null || e.getURL() == null)
return;
if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
OpenBrowser.displayUrl(e.getURL().toString());
}
}
});
scrollInfo = new JScrollPane(info, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
multiSplitPane.add(scrollInfo, "bottom");
return multiSplitPane;
}
/**
* Add the possible signs to the panel for selection
*/
private void fillSigns() {
pnlPossibleSigns.removeAll();
pnlPossibleSupplements.removeAll();
for (Sign s : signs) {
JLabel lbl = new JLabel(s.getIcon());
String tt = "<html>"+s.name;
String ref = s.getDefaultRef();
if (ref != null) {
tt += " <i><small>("+ref+")</small></i>";
}
tt += "</html>";
lbl.setToolTipText(tt);
s.label = lbl;
lbl.addMouseListener(new SignClickListener(s));
if (s.isSupplementing) {
pnlPossibleSupplements.add(lbl);
} else {
pnlPossibleSigns.add(lbl);
}
}
}
/**
* Represents a certain selection of signs by the user.
*
* Manages the update of gui elements when the selection changes.
*/
public class SignSelection {
private final LinkedList<SignCombination> combos = new LinkedList<>();
public void remove(SignCombination sc) {
int i = findIndex(sc);
combos.remove(i);
previewModel.update();
updatePanel(pnlSignSelection);
}
public void remove(SignWrapper sw) {
Pair<Integer, Integer> tmp = findIndex(sw);
int i = tmp.a;
int j = tmp.b;
if (j == 0) {
combos.remove(i);
previewModel.update();
updateSelectableSignsEnabledState();
updatePanel(pnlSignSelection);
} else {
combos.get(i).remove(j);
previewModel.update();
updateSelectableSignsEnabledState();
updatePanel(pnlSignSelection);
}
}
public void add(Sign sAdd) {
if (!sAdd.isSupplementing || combos.isEmpty()) {
SignCombination combo = new SignCombination();
combos.add(combo);
combo.add(sAdd);
previewModel.update();
updateSelectableSignsEnabledState();
updatePanel(pnlSignSelection);
} else {
SignCombination last = combos.getLast();
last.add(sAdd);
previewModel.update();
updatePanel(pnlSignSelection);
}
}
private int findIndex(SignCombination scFind) {
int i = 0;
for (SignCombination sc : combos) {
if (sc == scFind) {
return i;
}
i++;
}
throw new AssertionError("Could not find sign combination.");
}
private Pair<Integer, Integer> findIndex(SignWrapper swFind) {
int selIdx = 0;
for (SignCombination sc : combos) {
int combIdx = 0;
for (SignWrapper sw : sc.signs) {
if (swFind == sw) {
return new Pair<>(selIdx, combIdx);
}
combIdx++;
}
selIdx++;
}
throw new AssertionError("Could not find sign");
}
public void updatePanel(JPanel panel) {
panel.removeAll();
panel.setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.anchor = GridBagConstraints.NORTHWEST;
gbc.gridx = 0;
gbc.gridy = 0;
gbc.weightx = 0;
gbc.weighty = 1.0;
gbc.insets = new Insets(10, 10, 0, 10);
for (SignCombination sc : combos) {
JPanel pnlCombo = new JPanel(new GridBagLayout());
sc.updatePanel(pnlCombo);
panel.add(pnlCombo, gbc);
gbc.gridx++;
gbc.insets = new Insets(10, 0, 0, 10);
}
gbc.weightx = 1.0;
panel.add(new JLabel(""), gbc); /* filler */
panel.revalidate();
panel.repaint();
}
}
/**
* Describes a list of selected signs where the
* first sign is a normal or supplementary sign and the
* rest are all supplementary signs.
*
* It can fill a Panel with the necessary gui elements.
*/
public class SignCombination {
public LinkedList<SignWrapper> signs;
SignCombination() {
signs = new LinkedList<>();
}
public void updatePanel(JPanel panel) {
panel.removeAll();
panel.setLayout(new GridBagLayout());
Border etched = BorderFactory.createEtchedBorder(EtchedBorder.RAISED);
Border empty = BorderFactory.createEmptyBorder(3, 3, 3, 3);
panel.setBorder(BorderFactory.createCompoundBorder(etched, empty));
int i = 0;
for (SignWrapper sw : signs) {
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = i;
gbc.anchor = GridBagConstraints.NORTH;
panel.add(sw.getSignIcon(), gbc);
gbc.gridx = 1;
gbc.anchor = GridBagConstraints.WEST;
panel.add(sw.getParamsPanel(), gbc);
i++;
}
}
public void remove(int index) {
signs.remove(index);
}
public void add(final Sign sign) {
if (!signs.isEmpty() && !sign.isSupplementing)
throw new IllegalArgumentException("any sign but the first must be a supplement sign"); //FIXME
final SignWrapper signWrp = new SignWrapper(sign);
signs.add(signWrp);
}
}
/**
* Describes a single selected sign, including the parameters entered by
* the user.
*
* It provides the necessary gui elements.
*/
public class SignWrapper {
Sign sign;
JLabel signIcon;
String signRef;
JPanel paramsPanel;
Map<String, String> paramValues = new HashMap<>();
SignWrapper(Sign sign) {
this.sign = sign;
for (final SignParameter p : this.sign.params) {
paramValues.put(p.ident, p.getDefault());
}
}
@Override
public String toString() {
return sign.toString();
}
public JLabel getSignIcon() {
if (signIcon != null)
return signIcon;
signIcon = new JLabel(sign.getIcon());
signIcon.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
sel.remove(SignWrapper.this);
}
});
return signIcon;
}
public JPanel getParamsPanel() {
if (paramsPanel != null)
return paramsPanel;
paramsPanel = new JPanel(new GridBagLayout());
int i = 0;
for (final SignParameter p : this.sign.params) {
JPanel pnlInput = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
switch (p.input) {
case COMBO: // TODO
// create_gui_elements();
// break;
case TEXTFIELD:
final JTextField tf;
if (p.fieldWidth != null) {
tf = new JTextField(p.getDefault(), p.fieldWidth);
} else {
tf = new JTextField(p.getDefault());
}
class TFDocumentListener implements DocumentListener {
@Override
public void insertUpdate(DocumentEvent e) {
update();
}
@Override
public void removeUpdate(DocumentEvent e) {
update();
}
@Override
public void changedUpdate(DocumentEvent e) {
update();
}
public void update() {
paramValues.put(p.ident, tf.getText());
previewModel.update();
}
}
TFDocumentListener listener = new TFDocumentListener();
tf.getDocument().addDocumentListener(listener);
JLabel lblPrefix = new JLabel(p.getPrefix());
JLabel lblSuffix = new JLabel(p.getSuffix());
pnlInput.add(lblPrefix);
pnlInput.add(tf);
pnlInput.add(lblSuffix);
break;
default:
throw new RuntimeException();
}
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridy = i;
gbc.anchor = GridBagConstraints.WEST;
paramsPanel.add(pnlInput, gbc);
i++;
}
if (i > 0) {
paramsPanel.setBorder(BorderFactory.createEmptyBorder(0, 3, 0, 0));
}
return paramsPanel;
}
}
/**
* Give the user a hint, which supplementary signs fit the current selection.
* Disabled signs can still be clicked.
*/
private void updateSelectableSignsEnabledState() {
if (sel.combos.isEmpty()) {
for (Sign s : signs) {
if (s.isSupplementing) {
// TODO: only those that have no free parameter
s.label.setEnabled(true);
}
}
} else {
Sign main = sel.combos.getLast().signs.getFirst().sign;
for (Sign s : signs) {
if (s.isSupplementing) {
s.label.setEnabled(main.supplements.contains(s));
}
}
}
}
public JComponent buildPreviewPanel() {
JPanel previewPanel = new JPanel(new GridBagLayout());
String[] columnNames = {tr("Key"), tr("Value")};
String[][] data = {{}};
previewTable = new JTable(data, columnNames) {
@Override
public String getToolTipText(MouseEvent e) {
int rowIndex = rowAtPoint(e.getPoint());
int colIndex = columnAtPoint(e.getPoint());
if (rowIndex == -1 || colIndex == -1)
return null;
//int realColumnIndex = convertColumnIndexToModel(colIndex);
return (String) getValueAt(rowIndex, colIndex);
}
};
previewTable.setFillsViewportHeight(true);
previewTable.setRowSelectionAllowed(false);
previewTable.setColumnSelectionAllowed(false);
previewModel = new PreviewTableModel();
previewTable.setModel(previewModel);
JScrollPane scroll = new JScrollPane(previewTable);
Dimension dim = new Dimension(336, 10);
scroll.setPreferredSize(dim);
scroll.setMinimumSize(dim); /* minimum size is relevant for multisplit layout */
addTrafficSignTag = new JCheckBox(tr("{0} tag", "traffic_sign"));
addTrafficSignTag.setSelected(Main.pref.getBoolean("plugin.roadsigns.addTrafficSignTag"));
addTrafficSignTag.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
previewModel.update();
}
});
previewPanel.add(scroll, GBC.eol().fill());
previewPanel.add(addTrafficSignTag, GBC.eol());
return previewPanel;
}
public class PreviewTableModel extends AbstractTableModel {
private List<String> keys = new ArrayList<>();
private List<String> values = new ArrayList<>();
int rows = 3;
String[] header = {tr("Key"), tr("Value")};
@Override
public int getRowCount() {
return keys.size();
}
@Override
public int getColumnCount() {
return 2;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
if (columnIndex == 0) {
return keys.get(rowIndex);
} else if (columnIndex == 1) {
return values.get(rowIndex);
} else
throw new IllegalArgumentException();
}
@Override
public String getColumnName(int col) {
return header[col];
}
/**
* Analyse the selection and derive corresponding tags.
*/
public void update() {
final TreeMap<String, String> map = new TreeMap<>();
String traffic_sign = "";
for (SignCombination sc : sel.combos) {
final Map<String, String> env = new HashMap<>();
String combo_traffic_sign = "";
/**
* Keep track of a named tag. It may be changed by
* adding values or conditions.
*/
class TagEvaluater {
String key;
String default_value;
List<String> values = new ArrayList<>();
List<String> conditions = new ArrayList<>();
TagEvaluater(Tag t) {
key = t.key.evaluate(env);
default_value = t.value.evaluate(env);
}
public void append_value(String v) {
values.add(v);
}
public void condition(String c) {
conditions.add(c);
}
public Map<String, String> evaluate() {
String value = "";
if (values.isEmpty()) {
value = default_value;
} else {
String sep = "";
for (String v : values) {
value += sep+v;
sep = ";";
}
}
if (conditions.isEmpty())
return Collections.singletonMap(key, value);
else {
Map<String, String> result = new HashMap<>();
for (String c : conditions) {
result.put(key+":"+c, value);
}
return result;
}
}
}
Map<String, TagEvaluater> tags = new LinkedHashMap<>();
for (SignWrapper sw : sc.signs) {
for (Map.Entry<String, String> entry : sw.paramValues.entrySet()) {
env.put(entry.getKey(), entry.getValue());
}
if (sw.sign.ref != null) {
sw.signRef = sw.sign.ref.evaluate(env);
if (combo_traffic_sign.length() != 0) {
combo_traffic_sign += ",";
}
if (sw.sign.traffic_sign_tag != null) {
combo_traffic_sign += sw.sign.traffic_sign_tag.evaluate(env);
} else {
combo_traffic_sign += sw.signRef;
}
}
for (Tag t : sw.sign.tags) {
if (t.tag_ref != null) {
if (t.ident != null) {
env.put(t.ident+"_key", t.key.evaluate(env));
env.put(t.ident+"_value", t.value.evaluate(env));
}
if (t.append_value != null) {
TagEvaluater te = tags.get(t.tag_ref);
if (te == null) {
System.err.println(String.format("warning: referenced tag with ident '%s' not found for appending tag %s.",
t.tag_ref, t.toString()));
} else {
te.append_value(t.append_value.evaluate(env));
}
} else if (t.condition != null) {
TagEvaluater te = tags.get(t.tag_ref);
if (te == null) {
System.err.println(String.format("warning: referenced tag with ident '%s' not found for condition tag %s.",
t.tag_ref, t.toString()));
} else {
te.condition(t.condition.evaluate(env));
}
} else {
System.err.println(String.format("warning: found tag_ref but neither append_value nor condition for tag %s.",
t.toString()));
}
} else if (t.ident != null) {
env.put(t.ident+"_key", t.key.evaluate(env));
env.put(t.ident+"_value", t.value.evaluate(env));
if (tags.get(t.ident) != null) {
System.err.println(String.format("warning: tag identifier %s for %s already in use. ", t.ident, t.toString()));
}
tags.put(t.ident, new TagEvaluater(t));
} else {
map.put(t.key.evaluate(env), t.value.evaluate(env));
}
}
}
for (TagEvaluater te : tags.values()) {
Map<String, String> result = te.evaluate();
map.putAll(result);
}
if (combo_traffic_sign.length() != 0) {
if (traffic_sign.length() != 0) {
traffic_sign += ";";
}
traffic_sign += combo_traffic_sign;
}
}
if (addTrafficSignTag.isSelected()) {
map.put("traffic_sign", traffic_sign);
}
keys.clear();
values.clear();
for (Map.Entry<String, String> entry : map.entrySet()) {
if (!entry.getKey().isEmpty() && !entry.getValue().isEmpty()) {
keys.add(entry.getKey());
values.add(entry.getValue());
}
}
fireTableDataChanged();
}
}
/**
* Mouse events for the possible signs.
* Click selects it.
* MouseOver shows info.
*/
private class SignClickListener extends MouseAdapter {
private Sign sign;
SignClickListener(Sign sign) {
this.sign = sign;
}
@Override
public void mouseClicked(MouseEvent e) {
info.setText(longText());
/* scroll up again */
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
scrollInfo.getVerticalScrollBar().setValue(0);
}
});
sel.add(sign);
}
private String longText() {
StringBuilder txt = new StringBuilder();
txt.append(sign.long_name == null ? sign.name : sign.long_name);
String ref = sign.getDefaultRef();
if (ref != null) {
txt.append(" <i><small>("+ref+")</small></i>");
}
if (sign.help != null) {
txt.append("<p>");
txt.append(sign.help);
txt.append("</p>");
}
if (sign.wiki != null || sign.loc_wiki != null) {
String wikiPrefix = Main.pref.get("plugin.roadsigns.wikiprefix", "http://wiki.openstreetmap.org/wiki/");
txt.append("<p>");
if (sign.loc_wiki != null) {
String link = wikiPrefix+sign.loc_wiki;
txt.append("<a href=\""+link+"\">"+link+"</a>");
txt.append("<br>");
}
if (sign.wiki != null && !sign.wiki.equals(sign.loc_wiki)) {
String link = wikiPrefix+sign.wiki;
txt.append("<a href=\""+link+"\">"+link+"</a>");
}
txt.append("</p>");
}
return txt.toString();
}
}
/**
* Panel with FlowLayout that can be put inside a JScrollPane.
* (Normally it would not flow, but put all its children
* in a single row. This implementation respects the width of the parent
* component.)
*/
public static class FixedWidthPanel extends JPanel implements Scrollable {
FixedWidthPanel() {
super(new FlowLayout(FlowLayout.LEFT));
}
@Override
public void setBounds(int x, int y, int width, int height) {
super.setBounds(x, y, getParent().getWidth(), height);
}
@Override
public Dimension getPreferredSize() {
return new Dimension(getWidth(), getPreferredHeight());
}
@Override
public Dimension getPreferredScrollableViewportSize() {
return super.getPreferredSize();
}
@Override
public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
final int FRAC = 20;
int inc = (orientation == SwingConstants.VERTICAL ? getParent().getHeight() : getParent().getWidth()) / FRAC;
return Math.max(inc, 1);
}
@Override
public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
return orientation == SwingConstants.VERTICAL ? getParent().getHeight() : getParent().getWidth();
}
@Override
public boolean getScrollableTracksViewportWidth() {
return true;
}
@Override
public boolean getScrollableTracksViewportHeight() {
return false;
}
private int getPreferredHeight() {
int prefH = 0;
int num = getComponentCount();
for (int i = 0; i < num; ++i) {
Rectangle rect = getComponent(i).getBounds();
int h = rect.y + rect.height;
if (h > prefH) {
prefH = h;
}
}
prefH += ((FlowLayout) getLayout()).getVgap();
return prefH;
}
}
public static class SettingsPanel extends JPanel {
private List<PresetMetaData> presetsData;
private JComboBox<PresetMetaData> selectionBox;
JRadioButton rbAll, rbUseful;
SettingsPanel(boolean standalone, final Action update) {
super(new GridBagLayout());
presetsData = RoadSignsPlugin.getAvailablePresetsMetaData();
selectionBox = new JComboBox<>(presetsData.toArray(new PresetMetaData[0]));
String code = Main.pref.get("plugin.roadsigns.preset.selection", null);
if (code != null) {
for (PresetMetaData data : presetsData) {
if (code.equals(data.code)) {
selectionBox.setSelectedItem(data);
}
}
}
this.add(new JLabel(tr("Country preset:")), GBC.std().insets(5, 5, 5, 5));
this.add(selectionBox, GBC.eol().insets(0, 5, 5, 5));
if (!standalone) {
//String snd = "Hide signs that do not have an OSM tag assigned";
rbAll = new JRadioButton(tr("Show all signs"));
rbUseful = new JRadioButton(tr("Show a selection of the most useful signs"));
ButtonGroup grp = new ButtonGroup();
grp.add(rbAll);
grp.add(rbUseful);
String filterPref = Main.pref.get("plugin.roadsigns.preset.filter");
if (filterPref.equals("useful")) {
rbUseful.setSelected(true);
} else {
rbAll.setSelected(true);
}
JPanel pnFilter = new JPanel(new GridBagLayout());
pnFilter.setBorder(BorderFactory.createTitledBorder(tr("Filter")));
pnFilter.add(rbAll, GBC.eop());
pnFilter.add(rbUseful, GBC.eop());
this.add(pnFilter, GBC.eol().insets(5, 0, 5, 5));
JButton apply = new JButton(new AbstractAction(tr("Apply")) {
@Override
public void actionPerformed(ActionEvent e) {
try {
apply();
} catch (IOException ex) {
return;
}
update.actionPerformed(null);
}
});
this.add(apply, GBC.eol().insets(5, 0, 5, 5));
}
this.add(Box.createVerticalGlue(), GBC.eol().fill());
}
public void apply() throws IOException {
String filter = null;
if (rbAll != null) {
if (rbAll.isSelected()) {
filter = "all";
} else if (rbUseful.isSelected()) {
filter = "useful";
}
}
if (filter != null) {
Main.pref.put("plugin.roadsigns.preset.filter", filter);
}
RoadSignsPlugin.setSelectedPreset(presetsData.get(selectionBox.getSelectedIndex()));
}
}
}