001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.tagging.presets.items; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.awt.Component; 007import java.awt.GridBagLayout; 008import java.awt.Insets; 009import java.awt.event.ActionEvent; 010import java.awt.event.ActionListener; 011import java.text.NumberFormat; 012import java.text.ParseException; 013import java.util.Collection; 014import java.util.Collections; 015import java.util.List; 016 017import javax.swing.AbstractButton; 018import javax.swing.BorderFactory; 019import javax.swing.ButtonGroup; 020import javax.swing.JButton; 021import javax.swing.JComponent; 022import javax.swing.JLabel; 023import javax.swing.JPanel; 024import javax.swing.JToggleButton; 025 026import org.openstreetmap.josm.Main; 027import org.openstreetmap.josm.data.osm.OsmPrimitive; 028import org.openstreetmap.josm.data.osm.Tag; 029import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField; 030import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager; 031import org.openstreetmap.josm.gui.widgets.JosmComboBox; 032import org.openstreetmap.josm.gui.widgets.JosmTextField; 033import org.openstreetmap.josm.tools.GBC; 034 035/** 036 * Text field type. 037 */ 038public class Text extends KeyedItem { 039 040 private static int auto_increment_selected; 041 042 /** The localized version of {@link #text}. */ 043 public String locale_text; 044 public String default_; 045 public String originalValue; 046 public String use_last_as_default = "false"; 047 public String auto_increment; 048 public String length; 049 public String alternative_autocomplete_keys; 050 051 private JComponent value; 052 053 @Override 054 public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel, boolean presetInitiallyMatches) { 055 056 // find out if our key is already used in the selection. 057 Usage usage = determineTextUsage(sel, key); 058 AutoCompletingTextField textField = new AutoCompletingTextField(); 059 if (alternative_autocomplete_keys != null) { 060 initAutoCompletionField(textField, (key + ',' + alternative_autocomplete_keys).split(",")); 061 } else { 062 initAutoCompletionField(textField, key); 063 } 064 if (Main.pref.getBoolean("taggingpreset.display-keys-as-hint", true)) { 065 textField.setHint(key); 066 } 067 if (length != null && !length.isEmpty()) { 068 textField.setMaxChars(Integer.valueOf(length)); 069 } 070 if (usage.unused()) { 071 if (auto_increment_selected != 0 && auto_increment != null) { 072 try { 073 textField.setText(Integer.toString(Integer.parseInt( 074 LAST_VALUES.get(key)) + auto_increment_selected)); 075 } catch (NumberFormatException ex) { 076 // Ignore - cannot auto-increment if last was non-numeric 077 if (Main.isTraceEnabled()) { 078 Main.trace(ex.getMessage()); 079 } 080 } 081 } else if (!usage.hadKeys() || PROP_FILL_DEFAULT.get() || "force".equals(use_last_as_default)) { 082 // selected osm primitives are untagged or filling default values feature is enabled 083 if (!"false".equals(use_last_as_default) && LAST_VALUES.containsKey(key) && !presetInitiallyMatches) { 084 textField.setText(LAST_VALUES.get(key)); 085 } else { 086 textField.setText(default_); 087 } 088 } else { 089 // selected osm primitives are tagged and filling default values feature is disabled 090 textField.setText(""); 091 } 092 value = textField; 093 originalValue = null; 094 } else if (usage.hasUniqueValue()) { 095 // all objects use the same value 096 textField.setText(usage.getFirst()); 097 value = textField; 098 originalValue = usage.getFirst(); 099 } else { 100 // the objects have different values 101 JosmComboBox<String> comboBox = new JosmComboBox<>(usage.values.toArray(new String[0])); 102 comboBox.setEditable(true); 103 comboBox.setEditor(textField); 104 comboBox.getEditor().setItem(DIFFERENT); 105 value = comboBox; 106 originalValue = DIFFERENT; 107 } 108 if (locale_text == null) { 109 locale_text = getLocaleText(text, text_context, null); 110 } 111 112 // if there's an auto_increment setting, then wrap the text field 113 // into a panel, appending a number of buttons. 114 // auto_increment has a format like -2,-1,1,2 115 // the text box being the first component in the panel is relied 116 // on in a rather ugly fashion further down. 117 if (auto_increment != null) { 118 ButtonGroup bg = new ButtonGroup(); 119 JPanel pnl = new JPanel(new GridBagLayout()); 120 pnl.add(value, GBC.std().fill(GBC.HORIZONTAL)); 121 122 // first, one button for each auto_increment value 123 for (final String ai : auto_increment.split(",")) { 124 JToggleButton aibutton = new JToggleButton(ai); 125 aibutton.setToolTipText(tr("Select auto-increment of {0} for this field", ai)); 126 aibutton.setMargin(new java.awt.Insets(0, 0, 0, 0)); 127 aibutton.setFocusable(false); 128 saveHorizontalSpace(aibutton); 129 bg.add(aibutton); 130 try { 131 // TODO there must be a better way to parse a number like "+3" than this. 132 final int buttonvalue = (NumberFormat.getIntegerInstance().parse(ai.replace("+", ""))).intValue(); 133 if (auto_increment_selected == buttonvalue) aibutton.setSelected(true); 134 aibutton.addActionListener(new ActionListener() { 135 @Override 136 public void actionPerformed(ActionEvent e) { 137 auto_increment_selected = buttonvalue; 138 } 139 }); 140 pnl.add(aibutton, GBC.std()); 141 } catch (ParseException x) { 142 Main.error("Cannot parse auto-increment value of '" + ai + "' into an integer"); 143 } 144 } 145 146 // an invisible toggle button for "release" of the button group 147 final JToggleButton clearbutton = new JToggleButton("X"); 148 clearbutton.setVisible(false); 149 clearbutton.setFocusable(false); 150 bg.add(clearbutton); 151 // and its visible counterpart. - this mechanism allows us to 152 // have *no* button selected after the X is clicked, instead 153 // of the X remaining selected 154 JButton releasebutton = new JButton("X"); 155 releasebutton.setToolTipText(tr("Cancel auto-increment for this field")); 156 releasebutton.setMargin(new java.awt.Insets(0, 0, 0, 0)); 157 releasebutton.setFocusable(false); 158 releasebutton.addActionListener(new ActionListener() { 159 @Override 160 public void actionPerformed(ActionEvent e) { 161 auto_increment_selected = 0; 162 clearbutton.setSelected(true); 163 } 164 }); 165 saveHorizontalSpace(releasebutton); 166 pnl.add(releasebutton, GBC.eol()); 167 value = pnl; 168 } 169 p.add(new JLabel(locale_text+':'), GBC.std().insets(0, 0, 10, 0)); 170 p.add(value, GBC.eol().fill(GBC.HORIZONTAL)); 171 return true; 172 } 173 174 private static void saveHorizontalSpace(AbstractButton button) { 175 Insets insets = button.getBorder().getBorderInsets(button); 176 // Ensure the current look&feel does not waste horizontal space (as seen in Nimbus & Aqua) 177 if (insets != null && insets.left+insets.right > insets.top+insets.bottom) { 178 int min = Math.min(insets.top, insets.bottom); 179 button.setBorder(BorderFactory.createEmptyBorder(insets.top, min, insets.bottom, min)); 180 } 181 } 182 183 private static String getValue(Component comp) { 184 if (comp instanceof JosmComboBox) { 185 return ((JosmComboBox<?>) comp).getEditor().getItem().toString(); 186 } else if (comp instanceof JosmTextField) { 187 return ((JosmTextField) comp).getText(); 188 } else if (comp instanceof JPanel) { 189 return getValue(((JPanel) comp).getComponent(0)); 190 } else { 191 return null; 192 } 193 } 194 195 @Override 196 public void addCommands(List<Tag> changedTags) { 197 198 // return if unchanged 199 String v = getValue(value); 200 if (v == null) { 201 Main.error("No 'last value' support for component " + value); 202 return; 203 } 204 205 v = Tag.removeWhiteSpaces(v); 206 207 if (!"false".equals(use_last_as_default) || auto_increment != null) { 208 LAST_VALUES.put(key, v); 209 } 210 if (v.equals(originalValue) || (originalValue == null && v.isEmpty())) 211 return; 212 213 changedTags.add(new Tag(key, v)); 214 AutoCompletionManager.rememberUserInput(key, v, true); 215 } 216 217 @Override 218 public MatchType getDefaultMatch() { 219 return MatchType.NONE; 220 } 221 222 @Override 223 public Collection<String> getValues() { 224 if (default_ == null || default_.isEmpty()) 225 return Collections.emptyList(); 226 return Collections.singleton(default_); 227 } 228}