source: josm/trunk/src/org/openstreetmap/josm/gui/dialogs/properties/TagEditHelper.java@ 5886

Last change on this file since 5886 was 5886, checked in by Don-vip, 11 years ago

see #4429 - Right click menu "undo, cut, copy, paste, delete, select all" for each text component (originally based on patch by NooN)

File size: 33.4 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.dialogs.properties;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5import static org.openstreetmap.josm.tools.I18n.trn;
6
7import java.awt.BorderLayout;
8import java.awt.Component;
9import java.awt.Cursor;
10import java.awt.Dimension;
11import java.awt.FlowLayout;
12import java.awt.Font;
13import java.awt.GridBagConstraints;
14import java.awt.GridBagLayout;
15import java.awt.Toolkit;
16import java.awt.datatransfer.Clipboard;
17import java.awt.datatransfer.Transferable;
18import java.awt.event.ActionEvent;
19import java.awt.event.ActionListener;
20import java.awt.event.FocusAdapter;
21import java.awt.event.FocusEvent;
22import java.awt.event.InputEvent;
23import java.awt.event.KeyEvent;
24import java.awt.event.MouseAdapter;
25import java.awt.event.MouseEvent;
26import java.awt.event.WindowAdapter;
27import java.awt.event.WindowEvent;
28import java.awt.image.BufferedImage;
29import java.util.ArrayList;
30import java.util.Arrays;
31import java.util.Collection;
32import java.util.Collections;
33import java.util.Comparator;
34import java.util.HashMap;
35import java.util.Iterator;
36import java.util.LinkedHashMap;
37import java.util.LinkedList;
38import java.util.List;
39import java.util.Map;
40import java.util.Vector;
41
42import javax.swing.AbstractAction;
43import javax.swing.Action;
44import javax.swing.Box;
45import javax.swing.DefaultListCellRenderer;
46import javax.swing.ImageIcon;
47import javax.swing.JCheckBoxMenuItem;
48import javax.swing.JComponent;
49import javax.swing.JLabel;
50import javax.swing.JList;
51import javax.swing.JOptionPane;
52import javax.swing.JPanel;
53import javax.swing.JPopupMenu;
54import javax.swing.KeyStroke;
55import javax.swing.table.DefaultTableModel;
56import javax.swing.text.JTextComponent;
57
58import org.openstreetmap.josm.Main;
59import org.openstreetmap.josm.actions.JosmAction;
60import org.openstreetmap.josm.actions.mapmode.DrawAction;
61import org.openstreetmap.josm.command.ChangePropertyCommand;
62import org.openstreetmap.josm.command.Command;
63import org.openstreetmap.josm.command.SequenceCommand;
64import org.openstreetmap.josm.data.osm.DataSet;
65import org.openstreetmap.josm.data.osm.OsmPrimitive;
66import org.openstreetmap.josm.data.osm.Tag;
67import org.openstreetmap.josm.data.preferences.BooleanProperty;
68import org.openstreetmap.josm.data.preferences.IntegerProperty;
69import org.openstreetmap.josm.gui.ExtendedDialog;
70import org.openstreetmap.josm.gui.mappaint.MapPaintStyles;
71import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingComboBox;
72import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionListItem;
73import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager;
74import org.openstreetmap.josm.gui.util.GuiHelper;
75import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
76import org.openstreetmap.josm.io.XmlWriter;
77import org.openstreetmap.josm.tools.GBC;
78import org.openstreetmap.josm.tools.Shortcut;
79import org.openstreetmap.josm.tools.WindowGeometry;
80
81/**
82 * Class that helps PropertiesDialog add and edit tag values
83 */
84 class TagEditHelper {
85 private final DefaultTableModel propertyData;
86 private final Map<String, Map<String, Integer>> valueCount;
87
88 // Selection that we are editing by using both dialogs
89 Collection<OsmPrimitive> sel;
90
91 private String changedKey;
92 private String objKey;
93
94 Comparator<AutoCompletionListItem> defaultACItemComparator = new Comparator<AutoCompletionListItem>() {
95 public int compare(AutoCompletionListItem o1, AutoCompletionListItem o2) {
96 return String.CASE_INSENSITIVE_ORDER.compare(o1.getValue(), o2.getValue());
97 }
98 };
99
100 private String lastAddKey = null;
101 private String lastAddValue = null;
102
103 public static final int DEFAULT_LRU_TAGS_NUMBER = 5;
104 public static final int MAX_LRU_TAGS_NUMBER = 30;
105
106 // LRU cache for recently added tags (http://java-planet.blogspot.com/2005/08/how-to-set-up-simple-lru-cache-using.html)
107 private final Map<Tag, Void> recentTags = new LinkedHashMap<Tag, Void>(MAX_LRU_TAGS_NUMBER+1, 1.1f, true) {
108 @Override
109 protected boolean removeEldestEntry(Map.Entry<Tag, Void> eldest) {
110 return size() > MAX_LRU_TAGS_NUMBER;
111 }
112 };
113
114 TagEditHelper(DefaultTableModel propertyData, Map<String, Map<String, Integer>> valueCount) {
115 this.propertyData = propertyData;
116 this.valueCount = valueCount;
117 }
118
119 /**
120 * Open the add selection dialog and add a new key/value to the table (and
121 * to the dataset, of course).
122 */
123 public void addProperty() {
124 changedKey = null;
125 if (Main.map.mapMode instanceof DrawAction) {
126 sel = ((DrawAction) Main.map.mapMode).getInProgressSelection();
127 } else {
128 DataSet ds = Main.main.getCurrentDataSet();
129 if (ds == null) return;
130 sel = ds.getSelected();
131 }
132 if (sel.isEmpty()) return;
133
134 final AddTagsDialog addDialog = new AddTagsDialog();
135
136 addDialog.showDialog();
137
138 addDialog.destroyActions();
139 if (addDialog.getValue() == 1)
140 addDialog.performTagAdding();
141 else
142 addDialog.undoAllTagsAdding();
143 }
144
145 /**
146 * Edit the value in the properties table row
147 * @param row The row of the table from which the value is edited.
148 * @param focusOnKey Determines if the initial focus should be set on key instead of value
149 * @since 5653
150 */
151 public void editProperty(final int row, boolean focusOnKey) {
152 changedKey = null;
153 sel = Main.main.getCurrentDataSet().getSelected();
154 if (sel.isEmpty()) return;
155
156 String key = propertyData.getValueAt(row, 0).toString();
157 objKey=key;
158
159 final EditTagDialog editDialog = new EditTagDialog(key, row,
160 (Map<String, Integer>) propertyData.getValueAt(row, 1), focusOnKey);
161 editDialog.showDialog();
162 if (editDialog.getValue() !=1 ) return;
163 editDialog.performTagEdit();
164 }
165 /**
166 * If during last editProperty call user changed the key name, this key will be returned
167 * Elsewhere, returns null.
168 */
169 public String getChangedKey() {
170 return changedKey;
171 }
172
173 public void resetChangedKey() {
174 changedKey = null;
175 }
176
177 /**
178 * For a given key k, return a list of keys which are used as keys for
179 * auto-completing values to increase the search space.
180 * @param key the key k
181 * @return a list of keys
182 */
183 private static List<String> getAutocompletionKeys(String key) {
184 if ("name".equals(key) || "addr:street".equals(key))
185 return Arrays.asList("addr:street", "name");
186 else
187 return Arrays.asList(key);
188 }
189
190 /**
191 * Load recently used tags from preferences if needed
192 */
193 public void loadTagsIfNeeded() {
194 if (PROPERTY_REMEMBER_TAGS.get() && recentTags.isEmpty()) {
195 recentTags.clear();
196 Collection<String> c = Main.pref.getCollection("properties.recent-tags");
197 Iterator<String> it = c.iterator();
198 String key, value;
199 while (it.hasNext()) {
200 key = it.next();
201 value = it.next();
202 recentTags.put(new Tag(key, value), null);
203 }
204 }
205 }
206
207 /**
208 * Store recently used tags in preferences if needed
209 */
210 public void saveTagsIfNeeded() {
211 if (PROPERTY_REMEMBER_TAGS.get() && !recentTags.isEmpty()) {
212 List<String> c = new ArrayList<String>( recentTags.size()*2 );
213 for (Tag t: recentTags.keySet()) {
214 c.add(t.getKey());
215 c.add(t.getValue());
216 }
217 Main.pref.putCollection("properties.recent-tags", c);
218 }
219 }
220
221 public class EditTagDialog extends AbstractTagsDialog {
222 final String key;
223 final Map<String, Integer> m;
224 final int row;
225
226 Comparator<AutoCompletionListItem> usedValuesAwareComparator = new Comparator<AutoCompletionListItem>() {
227 @Override
228 public int compare(AutoCompletionListItem o1, AutoCompletionListItem o2) {
229 boolean c1 = m.containsKey(o1.getValue());
230 boolean c2 = m.containsKey(o2.getValue());
231 if (c1 == c2)
232 return String.CASE_INSENSITIVE_ORDER.compare(o1.getValue(), o2.getValue());
233 else if (c1)
234 return -1;
235 else
236 return +1;
237 }
238 };
239
240 DefaultListCellRenderer cellRenderer = new DefaultListCellRenderer() {
241 @Override public Component getListCellRendererComponent(JList list,
242 Object value, int index, boolean isSelected, boolean cellHasFocus){
243 Component c = super.getListCellRendererComponent(list, value,
244 index, isSelected, cellHasFocus);
245 if (c instanceof JLabel) {
246 String str = ((AutoCompletionListItem) value).getValue();
247 if (valueCount.containsKey(objKey)) {
248 Map<String, Integer> m = valueCount.get(objKey);
249 if (m.containsKey(str)) {
250 str = tr("{0} ({1})", str, m.get(str));
251 c.setFont(c.getFont().deriveFont(Font.ITALIC + Font.BOLD));
252 }
253 }
254 ((JLabel) c).setText(str);
255 }
256 return c;
257 }
258 };
259
260 private EditTagDialog(String key, int row, Map<String, Integer> map, final boolean initialFocusOnKey) {
261 super(Main.parent, trn("Change value?", "Change values?", map.size()), new String[] {tr("OK"),tr("Cancel")});
262 setButtonIcons(new String[] {"ok","cancel"});
263 setCancelButton(2);
264 configureContextsensitiveHelp("/Dialog/EditValue", true /* show help button */);
265 this.key = key;
266 this.row = row;
267 this.m = map;
268
269 JPanel mainPanel = new JPanel(new BorderLayout());
270
271 String msg = "<html>"+trn("This will change {0} object.",
272 "This will change up to {0} objects.", sel.size(), sel.size())
273 +"<br><br>("+tr("An empty value deletes the tag.", key)+")</html>";
274
275 mainPanel.add(new JLabel(msg), BorderLayout.NORTH);
276
277 JPanel p = new JPanel(new GridBagLayout());
278 mainPanel.add(p, BorderLayout.CENTER);
279
280 AutoCompletionManager autocomplete = Main.main.getEditLayer().data.getAutoCompletionManager();
281 List<AutoCompletionListItem> keyList = autocomplete.getKeys();
282 Collections.sort(keyList, defaultACItemComparator);
283
284 keys = new AutoCompletingComboBox(key);
285 keys.setPossibleACItems(keyList);
286 keys.setEditable(true);
287 keys.setSelectedItem(key);
288
289 p.add(Box.createVerticalStrut(5),GBC.eol());
290 p.add(new JLabel(tr("Key")), GBC.std());
291 p.add(Box.createHorizontalStrut(10), GBC.std());
292 p.add(keys, GBC.eol().fill(GBC.HORIZONTAL));
293
294 List<AutoCompletionListItem> valueList = autocomplete.getValues(getAutocompletionKeys(key));
295 Collections.sort(valueList, usedValuesAwareComparator);
296
297 final String selection= m.size()!=1?tr("<different>"):m.entrySet().iterator().next().getKey();
298
299 values = new AutoCompletingComboBox(selection);
300 values.setRenderer(cellRenderer);
301
302 values.setEditable(true);
303 values.setPossibleACItems(valueList);
304 values.setSelectedItem(selection);
305 values.getEditor().setItem(selection);
306 p.add(Box.createVerticalStrut(5),GBC.eol());
307 p.add(new JLabel(tr("Value")), GBC.std());
308 p.add(Box.createHorizontalStrut(10), GBC.std());
309 p.add(values, GBC.eol().fill(GBC.HORIZONTAL));
310 values.getEditor().addActionListener(new ActionListener() {
311 public void actionPerformed(ActionEvent e) {
312 buttonAction(0, null); // emulate OK button click
313 }
314 });
315 addFocusAdapter(autocomplete, usedValuesAwareComparator);
316
317 setContent(mainPanel, false);
318
319 addWindowListener(new WindowAdapter() {
320 @Override
321 public void windowOpened(WindowEvent e) {
322 if (initialFocusOnKey) {
323 selectKeysComboBox();
324 } else {
325 selectValuesCombobox();
326 }
327 }
328 });
329 }
330
331 /**
332 * Edit tags of multiple selected objects according to selected ComboBox values
333 * If value == "", tag will be deleted
334 * Confirmations may be needed.
335 */
336 private void performTagEdit() {
337 String value = values.getEditor().getItem().toString().trim();
338 // is not Java 1.5
339 //value = java.text.Normalizer.normalize(value, java.text.Normalizer.Form.NFC);
340 if (value.equals("")) {
341 value = null; // delete the key
342 }
343 String newkey = keys.getEditor().getItem().toString().trim();
344 //newkey = java.text.Normalizer.normalize(newkey, java.text.Normalizer.Form.NFC);
345 if (newkey.equals("")) {
346 newkey = key;
347 value = null; // delete the key instead
348 }
349 if (key.equals(newkey) && tr("<different>").equals(value))
350 return;
351 if (key.equals(newkey) || value == null) {
352 Main.main.undoRedo.add(new ChangePropertyCommand(sel, newkey, value));
353 } else {
354 for (OsmPrimitive osm: sel) {
355 if(osm.get(newkey) != null) {
356 ExtendedDialog ed = new ExtendedDialog(
357 Main.parent,
358 tr("Overwrite key"),
359 new String[]{tr("Replace"), tr("Cancel")});
360 ed.setButtonIcons(new String[]{"purge", "cancel"});
361 ed.setContent(tr("You changed the key from ''{0}'' to ''{1}''.\n"
362 + "The new key is already used, overwrite values?", key, newkey));
363 ed.setCancelButton(2);
364 ed.toggleEnable("overwriteEditKey");
365 ed.showDialog();
366
367 if (ed.getValue() != 1)
368 return;
369 break;
370 }
371 }
372 Collection<Command> commands=new Vector<Command>();
373 commands.add(new ChangePropertyCommand(sel, key, null));
374 if (value.equals(tr("<different>"))) {
375 HashMap<String, Vector<OsmPrimitive>> map=new HashMap<String, Vector<OsmPrimitive>>();
376 for (OsmPrimitive osm: sel) {
377 String val=osm.get(key);
378 if(val != null)
379 {
380 if (map.containsKey(val)) {
381 map.get(val).add(osm);
382 } else {
383 Vector<OsmPrimitive> v = new Vector<OsmPrimitive>();
384 v.add(osm);
385 map.put(val, v);
386 }
387 }
388 }
389 for (Map.Entry<String, Vector<OsmPrimitive>> e: map.entrySet()) {
390 commands.add(new ChangePropertyCommand(e.getValue(), newkey, e.getKey()));
391 }
392 } else {
393 commands.add(new ChangePropertyCommand(sel, newkey, value));
394 }
395 Main.main.undoRedo.add(new SequenceCommand(
396 trn("Change properties of up to {0} object",
397 "Change properties of up to {0} objects", sel.size(), sel.size()),
398 commands));
399 }
400
401 changedKey = newkey;
402 }
403 }
404
405 public static final BooleanProperty PROPERTY_FIX_TAG_LOCALE = new BooleanProperty("properties.fix-tag-combobox-locale", false);
406 public static final BooleanProperty PROPERTY_REMEMBER_TAGS = new BooleanProperty("properties.remember-recently-added-tags", false);
407 public static final IntegerProperty PROPERTY_RECENT_TAGS_NUMBER = new IntegerProperty("properties.recently-added-tags", DEFAULT_LRU_TAGS_NUMBER);
408
409 abstract class AbstractTagsDialog extends ExtendedDialog {
410 AutoCompletingComboBox keys;
411 AutoCompletingComboBox values;
412 Component componentUnderMouse;
413
414 public AbstractTagsDialog(Component parent, String title, String[] buttonTexts) {
415 super(parent, title, buttonTexts);
416 addMouseListener(new PopupMenuLauncher(popupMenu));
417 }
418
419 @Override
420 public void setupDialog() {
421 setResizable(false);
422 super.setupDialog();
423
424 setRememberWindowGeometry(getClass().getName() + ".geometry",
425 WindowGeometry.centerInWindow(Main.parent, getSize()));
426 }
427
428 @Override
429 public void setVisible(boolean visible) {
430 // Do not want dialog to be resizable, but its size may increase each time because of the recently added tags
431 // So need to modify the stored geometry (size part only) in order to use the automatic positioning mechanism
432 if (visible) {
433 WindowGeometry geometry = initWindowGeometry();
434 Dimension storedSize = geometry.getSize();
435 if (!storedSize.equals(getSize())) {
436 storedSize.setSize(getSize());
437 rememberWindowGeometry(geometry);
438 }
439 keys.setFixedLocale(PROPERTY_FIX_TAG_LOCALE.get());
440 }
441 super.setVisible(visible);
442 }
443
444 private void selectACComboBoxSavingUnixBuffer(AutoCompletingComboBox cb) {
445 // select compbobox with saving unix system selection (middle mouse paste)
446 Clipboard sysSel = Toolkit.getDefaultToolkit().getSystemSelection();
447 if(sysSel != null) {
448 Transferable old = sysSel.getContents(null);
449 cb.requestFocusInWindow();
450 cb.getEditor().selectAll();
451 sysSel.setContents(old, null);
452 } else {
453 cb.requestFocusInWindow();
454 cb.getEditor().selectAll();
455 }
456 }
457
458 public void selectKeysComboBox() {
459 selectACComboBoxSavingUnixBuffer(keys);
460 }
461
462 public void selectValuesCombobox() {
463 selectACComboBoxSavingUnixBuffer(values);
464 }
465
466 /**
467 * Create a focus handling adapter and apply in to the editor component of value
468 * autocompletion box.
469 * @param keys Box for keys entering and autocompletion
470 * @param values Box for values entering and autocompletion
471 * @param autocomplete Manager handling the autocompletion
472 * @param comparator Class to decide what values are offered on autocompletion
473 * @return The created adapter
474 */
475 protected FocusAdapter addFocusAdapter(final AutoCompletionManager autocomplete, final Comparator<AutoCompletionListItem> comparator) {
476 // get the combo box' editor component
477 JTextComponent editor = (JTextComponent)values.getEditor()
478 .getEditorComponent();
479 // Refresh the values model when focus is gained
480 FocusAdapter focus = new FocusAdapter() {
481 @Override public void focusGained(FocusEvent e) {
482 String key = keys.getEditor().getItem().toString();
483
484 List<AutoCompletionListItem> valueList = autocomplete.getValues(getAutocompletionKeys(key));
485 Collections.sort(valueList, comparator);
486
487 values.setPossibleACItems(valueList);
488 objKey=key;
489 }
490 };
491 editor.addFocusListener(focus);
492 return focus;
493 }
494
495 protected JPopupMenu popupMenu = new JPopupMenu() {
496 JCheckBoxMenuItem fixTagLanguageCb = new JCheckBoxMenuItem(
497 new AbstractAction(tr("Use English language for tag by default")){
498 public void actionPerformed(ActionEvent e) {
499 boolean sel=((JCheckBoxMenuItem) e.getSource()).getState();
500 PROPERTY_FIX_TAG_LOCALE.put(sel);
501 }
502 });
503 {
504 add(fixTagLanguageCb);
505 fixTagLanguageCb.setState(PROPERTY_FIX_TAG_LOCALE.get());
506 }
507 };
508 }
509
510 class AddTagsDialog extends AbstractTagsDialog {
511 List<JosmAction> recentTagsActions = new ArrayList<JosmAction>();
512
513 // Counter of added commands for possible undo
514 private int commandCount;
515
516 public AddTagsDialog() {
517 super(Main.parent, tr("Add value?"), new String[] {tr("OK"),tr("Cancel")});
518 setButtonIcons(new String[] {"ok","cancel"});
519 setCancelButton(2);
520 configureContextsensitiveHelp("/Dialog/AddValue", true /* show help button */);
521
522 JPanel mainPanel = new JPanel(new GridBagLayout());
523 keys = new AutoCompletingComboBox();
524 values = new AutoCompletingComboBox();
525
526 mainPanel.add(new JLabel("<html>"+trn("This will change up to {0} object.",
527 "This will change up to {0} objects.", sel.size(),sel.size())
528 +"<br><br>"+tr("Please select a key")), GBC.eol().fill(GBC.HORIZONTAL));
529
530 AutoCompletionManager autocomplete = Main.main.getEditLayer().data.getAutoCompletionManager();
531 List<AutoCompletionListItem> keyList = autocomplete.getKeys();
532
533 AutoCompletionListItem itemToSelect = null;
534 // remove the object's tag keys from the list
535 Iterator<AutoCompletionListItem> iter = keyList.iterator();
536 while (iter.hasNext()) {
537 AutoCompletionListItem item = iter.next();
538 if (item.getValue().equals(lastAddKey)) {
539 itemToSelect = item;
540 }
541 for (int i = 0; i < propertyData.getRowCount(); ++i) {
542 if (item.getValue().equals(propertyData.getValueAt(i, 0))) {
543 if (itemToSelect == item) {
544 itemToSelect = null;
545 }
546 iter.remove();
547 break;
548 }
549 }
550 }
551
552 Collections.sort(keyList, defaultACItemComparator);
553 keys.setPossibleACItems(keyList);
554 keys.setEditable(true);
555
556 mainPanel.add(keys, GBC.eop().fill());
557
558 mainPanel.add(new JLabel(tr("Please select a value")), GBC.eol());
559 values.setEditable(true);
560 mainPanel.add(values, GBC.eop().fill());
561 if (itemToSelect != null) {
562 keys.setSelectedItem(itemToSelect);
563 if (lastAddValue != null) {
564 values.setSelectedItem(lastAddValue);
565 }
566 }
567
568 FocusAdapter focus = addFocusAdapter(autocomplete, defaultACItemComparator);
569 // fire focus event in advance or otherwise the popup list will be too small at first
570 focus.focusGained(null);
571
572 int recentTagsToShow = PROPERTY_RECENT_TAGS_NUMBER.get();
573 if (recentTagsToShow > MAX_LRU_TAGS_NUMBER) {
574 recentTagsToShow = MAX_LRU_TAGS_NUMBER;
575 }
576
577 // Add tag on Shift-Enter
578 mainPanel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
579 KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, InputEvent.SHIFT_MASK), "addAndContinue");
580 mainPanel.getActionMap().put("addAndContinue", new AbstractAction() {
581 @Override
582 public void actionPerformed(ActionEvent e) {
583 performTagAdding();
584 selectKeysComboBox();
585 }
586 });
587
588 suggestRecentlyAddedTags(mainPanel, recentTagsToShow, focus);
589
590 setContent(mainPanel, false);
591
592 selectKeysComboBox();
593
594 popupMenu.add(new AbstractAction(tr("Set number of recently added tags")) {
595 public void actionPerformed(ActionEvent e) {
596 selectNumberOfTags();
597 }
598 });
599 JCheckBoxMenuItem rememberLastTags = new JCheckBoxMenuItem(
600 new AbstractAction(tr("Remember last used tags")){
601 public void actionPerformed(ActionEvent e) {
602 boolean sel=((JCheckBoxMenuItem) e.getSource()).getState();
603 PROPERTY_REMEMBER_TAGS.put(sel);
604 if (sel) saveTagsIfNeeded();
605 }
606 });
607 rememberLastTags.setState(PROPERTY_REMEMBER_TAGS.get());
608 popupMenu.add(rememberLastTags);
609 }
610
611 private void selectNumberOfTags() {
612 String s = JOptionPane.showInputDialog(this, tr("Please enter the number of recently added tags to display"));
613 if (s!=null) try {
614 int v = Integer.parseInt(s);
615 if (v>=0 && v<=MAX_LRU_TAGS_NUMBER) {
616 PROPERTY_RECENT_TAGS_NUMBER.put(v);
617 return;
618 }
619 } catch (NumberFormatException ex) { }
620 JOptionPane.showMessageDialog(this, tr("Please enter integer number between 0 and {0}", MAX_LRU_TAGS_NUMBER));
621
622 }
623
624 private void suggestRecentlyAddedTags(JPanel mainPanel, int tagsToShow, final FocusAdapter focus) {
625 if (!(tagsToShow > 0 && !recentTags.isEmpty()))
626 return;
627
628 mainPanel.add(new JLabel(tr("Recently added tags")), GBC.eol());
629
630 int count = 1;
631 // We store the maximum number (9) of recent tags to allow dynamic change of number of tags shown in the preferences.
632 // This implies to iterate in descending order, as the oldest elements will only be removed after we reach the maximum numbern and not the number of tags to show.
633 // However, as Set does not allow to iterate in descending order, we need to copy its elements into a List we can access in reverse order.
634 List<Tag> tags = new LinkedList<Tag>(recentTags.keySet());
635 for (int i = tags.size()-1; i >= 0 && count <= tagsToShow; i--, count++) {
636 final Tag t = tags.get(i);
637 // Create action for reusing the tag, with keyboard shortcut Ctrl+(1-5)
638 String actionShortcutKey = "properties:recent:"+count;
639 String actionShortcutShiftKey = "properties:recent:shift:"+count;
640 Shortcut sc = Shortcut.registerShortcut(actionShortcutKey, null, KeyEvent.VK_0+count, Shortcut.CTRL);
641 final JosmAction action = new JosmAction(actionShortcutKey, null, tr("Use this tag again"), sc, false) {
642 @Override
643 public void actionPerformed(ActionEvent e) {
644 keys.setSelectedItem(t.getKey());
645 // Update list of values (fix #7951)
646 // fix #8298 - update list of values before setting value (?)
647 focus.focusGained(null);
648 values.setSelectedItem(t.getValue());
649 selectValuesCombobox();
650 }
651 };
652 Shortcut scShift = Shortcut.registerShortcut(actionShortcutShiftKey, null, KeyEvent.VK_0+count, Shortcut.CTRL_SHIFT);
653 final JosmAction actionShift = new JosmAction(actionShortcutShiftKey, null, tr("Use this tag again"), scShift, false) {
654 @Override
655 public void actionPerformed(ActionEvent e) {
656 action.actionPerformed(null);
657 performTagAdding();
658 selectKeysComboBox();
659 }
660 };
661 recentTagsActions.add(action);
662 recentTagsActions.add(actionShift);
663 disableTagIfNeeded(t, action);
664 // Find and display icon
665 ImageIcon icon = MapPaintStyles.getNodeIcon(t, false); // Filters deprecated icon
666 if (icon == null) {
667 icon = new ImageIcon(new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB));
668 }
669 GridBagConstraints gbc = new GridBagConstraints();
670 gbc.ipadx = 5;
671 mainPanel.add(new JLabel(action.isEnabled() ? icon : GuiHelper.getDisabledIcon(icon)), gbc);
672 // Create tag label
673 final String color = action.isEnabled() ? "" : "; color:gray";
674 final JLabel tagLabel = new JLabel("<html>"
675 + "<style>td{border:1px solid gray; font-weight:normal"+color+"}</style>"
676 + "<table><tr><td>" + XmlWriter.encode(t.toString(), true) + "</td></tr></table></html>");
677 if (action.isEnabled()) {
678 // Register action
679 mainPanel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(sc.getKeyStroke(), actionShortcutKey);
680 mainPanel.getActionMap().put(actionShortcutKey, action);
681 mainPanel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(scShift.getKeyStroke(), actionShortcutShiftKey);
682 mainPanel.getActionMap().put(actionShortcutShiftKey, actionShift);
683 // Make the tag label clickable and set tooltip to the action description (this displays also the keyboard shortcut)
684 tagLabel.setToolTipText((String) action.getValue(Action.SHORT_DESCRIPTION));
685 tagLabel.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
686 tagLabel.addMouseListener(new MouseAdapter() {
687 @Override
688 public void mouseClicked(MouseEvent e) {
689 action.actionPerformed(null);
690 // add tags and close window on double-click
691 if (e.getClickCount()>1) {
692 performTagAdding();
693 buttonAction(0, null); // emulate OK click and close the dialog
694 }
695 // add tags on Shift-Click
696 if (e.isShiftDown()) {
697 performTagAdding();
698 selectKeysComboBox();
699 }
700 }
701 });
702 } else {
703 // Disable tag label
704 tagLabel.setEnabled(false);
705 // Explain in the tooltip why
706 tagLabel.setToolTipText(tr("The key ''{0}'' is already used", t.getKey()));
707 }
708 // Finally add label to the resulting panel
709 JPanel tagPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
710 tagPanel.add(tagLabel);
711 mainPanel.add(tagPanel, GBC.eol().fill(GBC.HORIZONTAL));
712 }
713 }
714
715 public void destroyActions() {
716 for (JosmAction action : recentTagsActions) {
717 action.destroy();
718 }
719 }
720
721
722 /**
723 * Read tags from comboboxes and add it to all selected objects
724 */
725 public void performTagAdding() {
726 String key = keys.getEditor().getItem().toString().trim();
727 String value = values.getEditor().getItem().toString().trim();
728 if (key.isEmpty() || value.isEmpty()) return;
729 lastAddKey = key;
730 lastAddValue = value;
731 recentTags.put(new Tag(key, value), null);
732 commandCount++;
733 Main.main.undoRedo.add(new ChangePropertyCommand(sel, key, value));
734 changedKey = key;
735 }
736
737
738 public void undoAllTagsAdding() {
739 Main.main.undoRedo.undo(commandCount);
740 }
741
742
743 private void disableTagIfNeeded(final Tag t, final JosmAction action) {
744 // Disable action if its key is already set on the object (the key being absent from the keys list for this reason
745 // performing this action leads to autocomplete to the next key (see #7671 comments)
746 for (int j = 0; j < propertyData.getRowCount(); ++j) {
747 if (t.getKey().equals(propertyData.getValueAt(j, 0))) {
748 action.setEnabled(false);
749 break;
750 }
751 }
752 }
753
754 }
755 }
Note: See TracBrowser for help on using the repository browser.