Changeset 18221 in josm for trunk/src/org/openstreetmap/josm/gui/dialogs
- Timestamp:
- 2021-09-13T00:41:53+02:00 (4 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/org/openstreetmap/josm/gui/dialogs/properties/TagEditHelper.java
r18215 r18221 7 7 import java.awt.BorderLayout; 8 8 import java.awt.Component; 9 import java.awt.ComponentOrientation; 9 10 import java.awt.Container; 10 11 import java.awt.Cursor; … … 14 15 import java.awt.GridBagConstraints; 15 16 import java.awt.GridBagLayout; 16 import java.awt.datatransfer.Clipboard;17 import java.awt.datatransfer.Transferable;18 17 import java.awt.event.ActionEvent; 19 import java.awt.event.FocusAdapter;20 18 import java.awt.event.FocusEvent; 19 import java.awt.event.FocusListener; 21 20 import java.awt.event.InputEvent; 22 21 import java.awt.event.KeyEvent; … … 45 44 import javax.swing.Box; 46 45 import javax.swing.ButtonGroup; 47 import javax.swing.ComboBoxModel;48 import javax.swing.DefaultListCellRenderer;49 46 import javax.swing.ImageIcon; 50 47 import javax.swing.JCheckBoxMenuItem; … … 61 58 import javax.swing.ListCellRenderer; 62 59 import javax.swing.SwingUtilities; 60 import javax.swing.event.PopupMenuEvent; 61 import javax.swing.event.PopupMenuListener; 63 62 import javax.swing.table.DefaultTableModel; 64 import javax.swing.text.JTextComponent;65 63 66 64 import org.openstreetmap.josm.actions.JosmAction; … … 87 85 import org.openstreetmap.josm.gui.IExtendedDialog; 88 86 import org.openstreetmap.josm.gui.MainApplication; 89 import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;90 87 import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBox; 88 import org.openstreetmap.josm.gui.tagging.ac.AutoCompEvent; 89 import org.openstreetmap.josm.gui.tagging.ac.AutoCompListener; 91 90 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager; 92 91 import org.openstreetmap.josm.gui.util.GuiHelper; 93 92 import org.openstreetmap.josm.gui.util.WindowGeometry; 93 import org.openstreetmap.josm.gui.widgets.JosmListCellRenderer; 94 import org.openstreetmap.josm.gui.widgets.OrientationAction; 94 95 import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher; 95 96 import org.openstreetmap.josm.io.XmlWriter; … … 116 117 117 118 private String changedKey; 118 private String objKey;119 119 120 120 static final Comparator<AutoCompletionItem> DEFAULT_AC_ITEM_COMPARATOR = … … 125 125 /** Maximum number of recent tags */ 126 126 public static final int MAX_LRU_TAGS_NUMBER = 30; 127 128 127 /** Autocomplete keys by default */ 129 128 public static final BooleanProperty AUTOCOMPLETE_KEYS = new BooleanProperty("properties.autocomplete-keys", true); … … 193 192 194 193 /** 194 * A custom list cell renderer that adds the value count to some items. 195 */ 196 static class TEHListCellRenderer extends JosmListCellRenderer<AutoCompletionItem> { 197 protected Map<String, Integer> map; 198 199 TEHListCellRenderer(Component component, ListCellRenderer<? super AutoCompletionItem> renderer, Map<String, Integer> map) { 200 super(component, renderer); 201 this.map = map; 202 } 203 204 @Override 205 public Component getListCellRendererComponent(JList<? extends AutoCompletionItem> list, AutoCompletionItem value, 206 int index, boolean isSelected, boolean cellHasFocus) { 207 Integer count = null; 208 // if there is a value count add it to the text 209 if (map != null) { 210 String text = value == null ? "" : value.toString(); 211 count = map.get(text); 212 if (count != null) { 213 value = new AutoCompletionItem(tr("{0} ({1})", text, count)); 214 } 215 } 216 Component l = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); 217 l.setComponentOrientation(component.getComponentOrientation()); 218 if (count != null) { 219 l.setFont(l.getFont().deriveFont(Font.ITALIC + Font.BOLD)); 220 } 221 return l; 222 } 223 } 224 225 /** 195 226 * Constructs a new {@code TagEditHelper}. 196 227 * @param tagTable tag table … … 284 315 return; 285 316 286 String key = getDataKey(row); 287 objKey = key; 288 289 final IEditTagDialog editDialog = getEditTagDialog(row, focusOnKey, key); 317 final IEditTagDialog editDialog = getEditTagDialog(row, focusOnKey, getDataKey(row)); 290 318 editDialog.showDialog(); 291 319 if (editDialog.getValue() != 1) … … 444 472 private final transient Map<String, Integer> m; 445 473 private final transient Comparator<AutoCompletionItem> usedValuesAwareComparator; 446 447 private final transient ListCellRenderer<AutoCompletionItem> cellRenderer = new ListCellRenderer<AutoCompletionItem>() { 448 private final DefaultListCellRenderer def = new DefaultListCellRenderer(); 449 @Override 450 public Component getListCellRendererComponent(JList<? extends AutoCompletionItem> list, 451 AutoCompletionItem value, int index, boolean isSelected, boolean cellHasFocus) { 452 Component c = def.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); 453 if (c instanceof JLabel) { 454 String str = value.getValue(); 455 if (valueCount.containsKey(objKey)) { 456 Map<String, Integer> map = valueCount.get(objKey); 457 if (map.containsKey(str)) { 458 str = tr("{0} ({1})", str, map.get(str)); 459 c.setFont(c.getFont().deriveFont(Font.ITALIC + Font.BOLD)); 460 } 461 } 462 ((JLabel) c).setText(str); 463 } 464 return c; 465 } 466 }; 467 468 protected EditTagDialog(String key, Map<String, Integer> map, final boolean initialFocusOnKey) { 474 private final transient AutoCompletionManager autocomplete; 475 476 protected EditTagDialog(String key, Map<String, Integer> map, boolean initialFocusOnKey) { 469 477 super(MainApplication.getMainFrame(), trn("Change value?", "Change values?", map.size()), tr("OK"), tr("Cancel")); 470 478 setButtonIcons("ok", "cancel"); … … 473 481 this.key = key; 474 482 this.m = map; 483 this.initialFocusOnKey = initialFocusOnKey; 475 484 476 485 usedValuesAwareComparator = (o1, o2) -> { … … 493 502 mainPanel.add(new JLabel(msg), BorderLayout.NORTH); 494 503 495 JPanel p = new JPanel(new GridBagLayout()); 504 JPanel p = new JPanel(new GridBagLayout()) { 505 /** 506 * This hack allows the comboboxes to have their own orientation. 507 * 508 * The problem is that 509 * {@link org.openstreetmap.josm.gui.ExtendedDialog#showDialog ExtendedDialog} calls 510 * {@code applyComponentOrientation} very late in the dialog construction process 511 * thus overwriting the orientation the components have chosen for themselves. 512 * 513 * This stops the propagation of {@code applyComponentOrientation}, thus all 514 * components may (and have to) set their own orientation. 515 */ 516 @Override 517 public void applyComponentOrientation(ComponentOrientation o) { 518 setComponentOrientation(o); 519 } 520 }; 496 521 mainPanel.add(p, BorderLayout.CENTER); 497 522 498 AutoCompletionManagerautocomplete = AutoCompletionManager.of(OsmDataManager.getInstance().getActiveDataSet());523 autocomplete = AutoCompletionManager.of(OsmDataManager.getInstance().getActiveDataSet()); 499 524 List<AutoCompletionItem> keyList = autocomplete.getTagKeys(DEFAULT_AC_ITEM_COMPARATOR); 500 525 501 526 keys = new AutoCompComboBox<>(); 502 527 keys.getModel().setComparator(Comparator.naturalOrder()); // according to Comparable 503 keys.setPrototypeDisplayValue(new AutoCompletionItem(key));504 528 keys.setEditable(true); 529 keys.setPrototypeDisplayValue(new AutoCompletionItem("dummy")); 505 530 keys.getModel().addAllElements(keyList); 506 keys.setSelectedItem(key); 531 keys.setSelectedItemText(key); 507 532 508 533 p.add(Box.createVerticalStrut(5), GBC.eol()); … … 517 542 values = new AutoCompComboBox<>(); 518 543 values.getModel().setComparator(Comparator.naturalOrder()); 519 values.setPrototypeDisplayValue(new AutoCompletionItem(selection)); 520 values.setRenderer(cellRenderer); 544 values.setRenderer(new TEHListCellRenderer(values, values.getRenderer(), valueCount.get(key))); 521 545 values.setEditable(true); 546 values.setPrototypeDisplayValue(new AutoCompletionItem("dummy")); 522 547 values.getModel().addAllElements(valueList); 523 values.setSelectedItem(selection); 524 values.getEditor().setItem(selection); 548 values.setSelectedItemText(selection); 525 549 526 550 p.add(Box.createVerticalStrut(5), GBC.eol()); … … 528 552 p.add(Box.createHorizontalStrut(10), GBC.std()); 529 553 p.add(values, GBC.eol().fill(GBC.HORIZONTAL)); 530 values.getEditor().addActionListener(e -> buttonAction(0, null)); 531 addFocusAdapter(autocomplete, usedValuesAwareComparator); 532 533 addUpdateIconListener(); 554 p.add(Box.createVerticalStrut(2), GBC.eol()); 555 556 p.applyComponentOrientation(OrientationAction.getDefaultComponentOrientation()); 557 keys.applyComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT); 558 values.applyComponentOrientation(OrientationAction.getNamelikeOrientation(keys.getText())); 534 559 535 560 setContent(mainPanel, false); 536 561 537 addWindowListener(new WindowAdapter() { 538 @Override 539 public void windowOpened(WindowEvent e) { 540 if (initialFocusOnKey) { 541 selectKeysComboBox(); 542 } else { 543 selectValuesCombobox(); 544 } 545 } 546 }); 562 addEventListeners(); 563 } 564 565 @Override 566 public void autoCompBefore(AutoCompEvent e) { 567 updateValueModel(autocomplete, usedValuesAwareComparator); 568 } 569 570 @Override 571 public void popupMenuWillBecomeVisible(PopupMenuEvent e) { 572 updateValueModel(autocomplete, usedValuesAwareComparator); 547 573 } 548 574 … … 600 626 } 601 627 602 protected abstract class AbstractTagsDialog extends ExtendedDialog { 628 protected abstract class AbstractTagsDialog extends ExtendedDialog implements AutoCompListener, FocusListener, PopupMenuListener { 603 629 protected AutoCompComboBox<AutoCompletionItem> keys; 604 630 protected AutoCompComboBox<AutoCompletionItem> values; 631 protected boolean initialFocusOnKey = true; 632 /** 633 * The 'values' model is currently holding values for this key. Used for lazy-loading of values. 634 */ 635 protected String currentValuesModelKey = ""; 605 636 606 637 AbstractTagsDialog(Component parent, String title, String... buttonTexts) { … … 623 654 setRememberWindowGeometry(getClass().getName() + ".geometry", 624 655 WindowGeometry.centerInWindow(MainApplication.getMainFrame(), size)); 656 keys.setFixedLocale(PROPERTY_FIX_TAG_LOCALE.get()); 625 657 } 626 658 … … 642 674 rememberWindowGeometry(geometry); 643 675 } 644 keys.setFixedLocale(PROPERTY_FIX_TAG_LOCALE.get());645 676 updateOkButtonIcon(); 646 677 } … … 648 679 } 649 680 650 private void selectACComboBoxSavingUnixBuffer(AutoCompComboBox<AutoCompletionItem> cb) {651 // select combobox with saving unix system selection (middle mouse paste)652 Clipboard sysSel = ClipboardUtils.getSystemSelection();653 if (sysSel != null) {654 Transferable old = ClipboardUtils.getClipboardContent(sysSel);655 cb.requestFocusInWindow();656 cb.getEditor().selectAll();657 if (old != null) {658 sysSel.setContents(old, null);659 }660 } else {661 cb.requestFocusInWindow();662 cb.getEditor().selectAll();663 }664 }665 666 public void selectKeysComboBox() {667 selectACComboBoxSavingUnixBuffer(keys);668 }669 670 public void selectValuesCombobox() {671 selectACComboBoxSavingUnixBuffer(values);672 }673 674 681 /** 675 * Create a focus handling adapter and apply in to the editor component of value 676 * autocompletion box. 677 * @param autocomplete Manager handling the autocompletion 678 * @param comparator Class to decide what values are offered on autocompletion 679 * @return The created adapter 680 */ 681 protected FocusAdapter addFocusAdapter(final AutoCompletionManager autocomplete, final Comparator<AutoCompletionItem> comparator) { 682 // get the combo box' editor component 683 final JTextComponent editor = values.getEditorComponent(); 684 // Refresh the values model when focus is gained 685 FocusAdapter focus = new FocusAdapter() { 686 @Override 687 public void focusGained(FocusEvent e) { 688 Logging.trace("Focus gained by {0}, e={1}", values, e); 689 String key = keys.getEditor().getItem().toString(); 690 List<AutoCompletionItem> correctItems = autocomplete.getTagValues(getAutocompletionKeys(key), comparator); 691 ComboBoxModel<AutoCompletionItem> currentModel = values.getModel(); 692 final int size = correctItems.size(); 693 boolean valuesOK = size == currentModel.getSize() 694 && IntStream.range(0, size).allMatch(i -> Objects.equals(currentModel.getElementAt(i), correctItems.get(i))); 695 if (!valuesOK) { 696 values.getModel().removeAllElements(); 697 values.getModel().addAllElements(correctItems); 698 } 699 if (!Objects.equals(key, objKey)) { 700 values.getEditor().selectAll(); 701 objKey = key; 702 } 703 } 704 }; 705 editor.addFocusListener(focus); 706 return focus; 707 } 708 709 protected void addUpdateIconListener() { 710 keys.addActionListener(ignore -> updateOkButtonIcon()); 711 values.addActionListener(ignore -> updateOkButtonIcon()); 712 } 713 714 private void updateOkButtonIcon() { 682 * Updates the values model if the key has changed 683 * 684 * @param autocomplete the autocompletion manager 685 * @param comparator sorting order for the items in the combo dropdown 686 */ 687 protected void updateValueModel(AutoCompletionManager autocomplete, Comparator<AutoCompletionItem> comparator) { 688 String key = keys.getText(); 689 if (!key.equals(currentValuesModelKey)) { 690 Logging.debug("updateValueModel: lazy loading values for key ''{0}''", key); 691 // key has changed, reload model 692 String savedText = values.getText(); 693 values.getModel().removeAllElements(); 694 values.getModel().addAllElements(autocomplete.getTagValues(getAutocompletionKeys(key), comparator)); 695 values.applyComponentOrientation(OrientationAction.getNamelikeOrientation(key)); 696 values.setSelectedItemText(savedText); 697 values.getEditor().selectAll(); 698 currentValuesModelKey = key; 699 } 700 } 701 702 protected void addEventListeners() { 703 // OK on Enter in values 704 values.getEditor().addActionListener(e -> buttonAction(0, null)); 705 // update values orientation according to key 706 keys.getEditorComponent().addFocusListener(this); 707 // update the "values" data model before an autocomplete or list dropdown 708 values.getEditorComponent().addAutoCompListener(this); 709 values.addPopupMenuListener(this); 710 // set the initial focus to either combobox 711 addWindowListener(new WindowAdapter() { 712 @Override 713 public void windowOpened(WindowEvent e) { 714 if (initialFocusOnKey) { 715 keys.requestFocus(); 716 } else { 717 values.requestFocus(); 718 } 719 } 720 }); 721 } 722 723 @Override 724 public void autoCompPerformed(AutoCompEvent e) { 725 } 726 727 @Override 728 public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { 729 } 730 731 @Override 732 public void popupMenuCanceled(PopupMenuEvent e) { 733 } 734 735 @Override 736 public void focusGained(FocusEvent e) { 737 } 738 739 @Override 740 public void focusLost(FocusEvent e) { 741 // update the values combobox orientation if the key changed 742 values.applyComponentOrientation(OrientationAction.getNamelikeOrientation(keys.getText())); 743 } 744 745 protected void updateOkButtonIcon() { 715 746 if (buttons.isEmpty()) { 716 747 return; … … 746 777 protected class AddTagsDialog extends AbstractTagsDialog { 747 778 private final List<JosmAction> recentTagsActions = new ArrayList<>(); 748 protected final transient FocusAdapter focus;749 779 private final JPanel mainPanel; 750 780 private JPanel recentTagsPanel; … … 752 782 // Counter of added commands for possible undo 753 783 private int commandCount; 784 private final transient AutoCompletionManager autocomplete; 754 785 755 786 protected AddTagsDialog() { … … 759 790 configureContextsensitiveHelp("/Dialog/AddValue", true /* show help button */); 760 791 761 mainPanel = new JPanel(new GridBagLayout()); 762 keys = new AutoCompComboBox<>(); 763 values = new AutoCompComboBox<>(); 764 keys.getModel().setComparator(Comparator.naturalOrder()); // according to Comparable 765 values.getModel().setComparator(Comparator.naturalOrder()); 766 keys.setPrototypeDisplayValue(new AutoCompletionItem("dummy")); 767 values.setPrototypeDisplayValue(new AutoCompletionItem("dummy")); 768 keys.setAutocompleteEnabled(AUTOCOMPLETE_KEYS.get()); 769 values.setAutocompleteEnabled(AUTOCOMPLETE_VALUES.get()); 770 792 mainPanel = new JPanel(new GridBagLayout()) { 793 /** 794 * This hack allows the comboboxes to have their own orientation. 795 * 796 * The problem is that 797 * {@link org.openstreetmap.josm.gui.ExtendedDialog#showDialog ExtendedDialog} calls 798 * {@code applyComponentOrientation} very late in the dialog construction process 799 * thus overwriting the orientation the components have chosen for themselves. 800 * 801 * This stops the propagation of {@code applyComponentOrientation}, thus all 802 * components may (and have to) set their own orientation. 803 */ 804 @Override 805 public void applyComponentOrientation(ComponentOrientation o) { 806 setComponentOrientation(o); 807 } 808 }; 771 809 mainPanel.add(new JLabel("<html>"+trn("This will change up to {0} object.", 772 810 "This will change up to {0} objects.", sel.size(), sel.size()) 773 811 +"<br><br>"+tr("Please select a key")), GBC.eol().fill(GBC.HORIZONTAL)); 774 812 813 keys = new AutoCompComboBox<>(); 814 keys.setPrototypeDisplayValue(new AutoCompletionItem("dummy")); 815 keys.setEditable(true); 816 keys.getModel().setComparator(Comparator.naturalOrder()); // according to Comparable 817 keys.setAutocompleteEnabled(AUTOCOMPLETE_KEYS.get()); 818 819 mainPanel.add(keys, GBC.eop().fill(GBC.HORIZONTAL)); 820 mainPanel.add(new JLabel(tr("Choose a value")), GBC.eol()); 821 822 values = new AutoCompComboBox<>(); 823 values.setPrototypeDisplayValue(new AutoCompletionItem("dummy")); 824 values.setEditable(true); 825 values.getModel().setComparator(Comparator.naturalOrder()); 826 values.setAutocompleteEnabled(AUTOCOMPLETE_VALUES.get()); 827 828 mainPanel.add(values, GBC.eop().fill(GBC.HORIZONTAL)); 829 775 830 cacheRecentTags(); 776 AutoCompletionManagerautocomplete = AutoCompletionManager.of(OsmDataManager.getInstance().getActiveDataSet());831 autocomplete = AutoCompletionManager.of(OsmDataManager.getInstance().getActiveDataSet()); 777 832 List<AutoCompletionItem> keyList = autocomplete.getTagKeys(DEFAULT_AC_ITEM_COMPARATOR); 778 833 … … 780 835 keyList.removeIf(item -> containsDataKey(item.getValue())); 781 836 782 keys.getModel().removeAllElements();783 837 keys.getModel().addAllElements(keyList); 784 keys.setEditable(true); 785 786 mainPanel.add(keys, GBC.eop().fill(GBC.HORIZONTAL)); 787 788 mainPanel.add(new JLabel(tr("Choose a value")), GBC.eol()); 789 values.setEditable(true); 790 mainPanel.add(values, GBC.eop().fill(GBC.HORIZONTAL)); 838 839 updateValueModel(autocomplete, DEFAULT_AC_ITEM_COMPARATOR); 791 840 792 841 // pre-fill first recent tag for which the key is not already present … … 795 844 .findFirst() 796 845 .ifPresent(tag -> { 797 keys.setSelectedItem(tag.getKey()); 798 values.setSelectedItem(tag.getValue()); 846 keys.setSelectedItemText(tag.getKey()); 847 values.setSelectedItemText(tag.getValue()); 799 848 }); 800 849 801 focus = addFocusAdapter(autocomplete, DEFAULT_AC_ITEM_COMPARATOR); 802 // fire focus event in advance or otherwise the popup list will be too small at first 803 focus.focusGained(new FocusEvent(this, FocusEvent.FOCUS_GAINED)); 804 805 addUpdateIconListener(); 850 851 keys.addActionListener(ignore -> updateOkButtonIcon()); 852 values.addActionListener(ignore -> updateOkButtonIcon()); 806 853 807 854 // Add tag on Shift-Enter … … 813 860 performTagAdding(); 814 861 refreshRecentTags(); 815 selectKeysComboBox();862 keys.requestFocus(); 816 863 } 817 864 }); … … 820 867 821 868 mainPanel.add(Box.createVerticalGlue(), GBC.eop().fill()); 869 mainPanel.applyComponentOrientation(OrientationAction.getDefaultComponentOrientation()); 870 822 871 setContent(mainPanel, false); 823 872 824 selectKeysComboBox();873 addEventListeners(); 825 874 826 875 popupMenu.add(new AbstractAction(tr("Set number of recently added tags")) { … … 847 896 rememberLastTags.setState(PROPERTY_REMEMBER_TAGS.get()); 848 897 popupMenu.add(rememberLastTags); 898 } 899 900 @Override 901 public void autoCompBefore(AutoCompEvent e) { 902 updateValueModel(autocomplete, DEFAULT_AC_ITEM_COMPARATOR); 903 } 904 905 @Override 906 public void popupMenuWillBecomeVisible(PopupMenuEvent e) { 907 updateValueModel(autocomplete, DEFAULT_AC_ITEM_COMPARATOR); 849 908 } 850 909 … … 972 1031 @Override 973 1032 public void actionPerformed(ActionEvent e) { 974 keys.setSelectedItem(t.getKey()); 1033 keys.setSelectedItemText(t.getKey()); 975 1034 // fix #7951, #8298 - update list of values before setting value (?) 976 focus.focusGained(new FocusEvent(AddTagsDialog.this, FocusEvent.FOCUS_GAINED));977 values.setSelectedItem(t.getValue()); 978 selectValuesCombobox();1035 updateValueModel(autocomplete, DEFAULT_AC_ITEM_COMPARATOR); 1036 values.setSelectedItemText(t.getValue()); 1037 values.requestFocus(); 979 1038 } 980 1039 }; … … 989 1048 performTagAdding(); 990 1049 refreshRecentTags(); 991 selectKeysComboBox();1050 keys.requestFocus(); 992 1051 } 993 1052 }; … … 1037 1096 performTagAdding(); 1038 1097 refreshRecentTags(); 1039 selectKeysComboBox();1098 keys.requestFocus(); 1040 1099 } else if (e.getClickCount() > 1) { 1041 1100 // add tags and close window on double-click
Note:
See TracChangeset
for help on using the changeset viewer.