Changeset 18221 in josm for trunk/src/org
- Timestamp:
- 2021-09-13T00:41:53+02:00 (3 years ago)
- Location:
- trunk/src/org/openstreetmap/josm/gui
- Files:
-
- 9 added
- 29 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/org/openstreetmap/josm/gui/conflict/pair/AbstractListMergeModel.java
r16824 r18221 22 22 import java.util.stream.IntStream; 23 23 24 import javax.swing.AbstractListModel;25 import javax.swing.ComboBoxModel;26 24 import javax.swing.DefaultListSelectionModel; 27 25 import javax.swing.JOptionPane; … … 42 40 import org.openstreetmap.josm.gui.util.ChangeNotifier; 43 41 import org.openstreetmap.josm.gui.util.TableHelper; 42 import org.openstreetmap.josm.gui.widgets.JosmComboBoxModel; 44 43 import org.openstreetmap.josm.gui.widgets.OsmPrimitivesTableModel; 45 44 import org.openstreetmap.josm.tools.CheckParameterUtil; … … 833 832 } 834 833 835 public class ComparePairListModel extends AbstractListModel<ComparePairType> implementsComboBoxModel<ComparePairType> {834 public class ComparePairListModel extends JosmComboBoxModel<ComparePairType> { 836 835 837 836 private int selectedIdx; -
trunk/src/org/openstreetmap/josm/gui/conflict/tags/MultiValueCellEditor.java
r17333 r18221 13 13 14 14 import javax.swing.AbstractCellEditor; 15 import javax.swing.DefaultComboBoxModel;16 15 import javax.swing.JLabel; 17 16 import javax.swing.JList; … … 22 21 23 22 import org.openstreetmap.josm.gui.widgets.JosmComboBox; 23 import org.openstreetmap.josm.gui.widgets.JosmComboBoxModel; 24 24 import org.openstreetmap.josm.tools.Logging; 25 25 … … 50 50 /** the combo box used as editor */ 51 51 private final JosmComboBox<Object> editor; 52 private final DefaultComboBoxModel<Object> editorModel;52 private final JosmComboBoxModel<Object> editorModel; 53 53 private final CopyOnWriteArrayList<NavigationListener> listeners; 54 54 … … 87 87 */ 88 88 public MultiValueCellEditor() { 89 editorModel = new DefaultComboBoxModel<>();89 editorModel = new JosmComboBoxModel<>(); 90 90 editor = new JosmComboBox<Object>(editorModel) { 91 91 @Override -
trunk/src/org/openstreetmap/josm/gui/conflict/tags/MultiValueCellRenderer.java
r12620 r18221 7 7 import java.awt.Font; 8 8 9 import javax.swing.DefaultComboBoxModel;10 9 import javax.swing.ImageIcon; 11 10 import javax.swing.JLabel; … … 16 15 import org.openstreetmap.josm.gui.conflict.ConflictColors; 17 16 import org.openstreetmap.josm.gui.widgets.JosmComboBox; 17 import org.openstreetmap.josm.gui.widgets.JosmComboBoxModel; 18 18 import org.openstreetmap.josm.tools.ImageProvider; 19 19 import org.openstreetmap.josm.tools.Logging; … … 27 27 private final ImageIcon iconDecided; 28 28 private final ImageIcon iconUndecided; 29 private final DefaultComboBoxModel<Object> model;29 private final JosmComboBoxModel<Object> model; 30 30 private final JosmComboBox<Object> cbDecisionRenderer; 31 31 … … 37 37 iconDecided = ImageProvider.get("dialogs/conflict", "tagconflictresolved"); 38 38 iconUndecided = ImageProvider.get("dialogs/conflict", "tagconflictunresolved"); 39 model = new DefaultComboBoxModel<>();39 model = new JosmComboBoxModel<>(); 40 40 cbDecisionRenderer = new JosmComboBox<>(model); 41 41 } -
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 -
trunk/src/org/openstreetmap/josm/gui/io/BasicUploadSettingsPanel.java
r18174 r18221 108 108 109 109 hcbUploadComment.setToolTipText(tr("Enter an upload comment")); 110 hcbUploadComment. setMaxTextLength(Changeset.MAX_CHANGESET_TAG_LENGTH);110 hcbUploadComment.getEditorComponent().setMaxTextLength(Changeset.MAX_CHANGESET_TAG_LENGTH); 111 111 JTextField editor = hcbUploadComment.getEditorComponent(); 112 112 editor.getDocument().putProperty("tag", "comment"); … … 147 147 148 148 hcbUploadSource.setToolTipText(tr("Enter a source")); 149 hcbUploadSource. setMaxTextLength(Changeset.MAX_CHANGESET_TAG_LENGTH);149 hcbUploadSource.getEditorComponent().setMaxTextLength(Changeset.MAX_CHANGESET_TAG_LENGTH); 150 150 JTextField editor = hcbUploadSource.getEditorComponent(); 151 151 editor.getDocument().putProperty("tag", "source"); -
trunk/src/org/openstreetmap/josm/gui/io/OpenChangesetComboBoxModel.java
r18208 r18221 5 5 import java.util.List; 6 6 7 import javax.swing.DefaultComboBoxModel;8 9 7 import org.openstreetmap.josm.data.osm.Changeset; 10 8 import org.openstreetmap.josm.data.osm.ChangesetCache; … … 12 10 import org.openstreetmap.josm.data.osm.ChangesetCacheListener; 13 11 import org.openstreetmap.josm.gui.util.GuiHelper; 12 import org.openstreetmap.josm.gui.widgets.JosmComboBoxModel; 14 13 import org.openstreetmap.josm.tools.Utils; 15 14 … … 19 18 * 20 19 */ 21 public class OpenChangesetComboBoxModel extends DefaultComboBoxModel<Changeset> implements ChangesetCacheListener {20 public class OpenChangesetComboBoxModel extends JosmComboBoxModel<Changeset> implements ChangesetCacheListener { 22 21 private final transient List<Changeset> changesets; 23 22 private transient Changeset selectedChangeset; … … 79 78 80 79 @Override 81 public int getIndexOf( Object anObject) {80 public int getIndexOf(Changeset anObject) { 82 81 return changesets.indexOf(anObject); 83 82 } -
trunk/src/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImages.java
r18196 r18221 40 40 import javax.swing.JPanel; 41 41 import javax.swing.JSeparator; 42 import javax.swing.MutableComboBoxModel;43 42 import javax.swing.SwingConstants; 44 43 import javax.swing.event.ChangeEvent; … … 71 70 import org.openstreetmap.josm.gui.layer.gpx.GpxDataHelper; 72 71 import org.openstreetmap.josm.gui.widgets.JosmComboBox; 72 import org.openstreetmap.josm.gui.widgets.JosmComboBoxModel; 73 73 import org.openstreetmap.josm.gui.widgets.JosmTextField; 74 74 import org.openstreetmap.josm.spi.preferences.Config; … … 86 86 public class CorrelateGpxWithImages extends AbstractAction implements ExpertModeChangeListener, Destroyable { 87 87 88 private static MutableComboBoxModel<GpxDataWrapper> gpxModel;88 private static JosmComboBoxModel<GpxDataWrapper> gpxModel; 89 89 private static boolean forceTags; 90 90 … … 424 424 */ 425 425 private void constructGpxModel(NoGpxDataWrapper nogdw) { 426 gpxModel = new DefaultComboBoxModel<>();426 gpxModel = new JosmComboBoxModel<>(); 427 427 GpxDataWrapper defaultItem = null; 428 428 for (AbstractModifiableLayer cur : MainApplication.getLayerManager().getLayersOfType(AbstractModifiableLayer.class)) { -
trunk/src/org/openstreetmap/josm/gui/preferences/display/LanguagePreference.java
r17648 r18221 12 12 13 13 import javax.swing.Box; 14 import javax.swing.DefaultComboBoxModel;15 14 import javax.swing.DefaultListCellRenderer; 16 15 import javax.swing.JLabel; … … 25 24 import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane; 26 25 import org.openstreetmap.josm.gui.widgets.JosmComboBox; 26 import org.openstreetmap.josm.gui.widgets.JosmComboBoxModel; 27 27 import org.openstreetmap.josm.spi.preferences.Config; 28 28 import org.openstreetmap.josm.tools.GBC; … … 82 82 } 83 83 84 private static class LanguageComboBoxModel extends DefaultComboBoxModel<Locale> {84 private static class LanguageComboBoxModel extends JosmComboBoxModel<Locale> { 85 85 private final List<Locale> data = new ArrayList<>(); 86 86 -
trunk/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompComboBox.java
r18193 r18221 2 2 package org.openstreetmap.josm.gui.tagging.ac; 3 3 4 import java.awt.datatransfer.Clipboard;5 import java.awt.datatransfer.StringSelection;6 import java.awt.datatransfer.Transferable;7 import java.awt.event.FocusEvent;8 import java.awt.event.FocusListener;9 import java.awt.event.KeyEvent;10 import java.awt.event.KeyListener;11 4 import java.awt.im.InputContext; 12 5 import java.util.Collection; … … 14 7 import java.util.LinkedList; 15 8 import java.util.Locale; 16 import java.util.Objects; 17 import java.util.regex.Pattern; 18 19 import javax.swing.JTextField; 20 import javax.swing.SwingUtilities; 21 import javax.swing.text.AbstractDocument; 22 import javax.swing.text.AttributeSet; 23 import javax.swing.text.BadLocationException; 24 import javax.swing.text.DocumentFilter; 25 import javax.swing.text.JTextComponent; 26 import javax.swing.text.StyleConstants; 27 28 import org.openstreetmap.josm.gui.MainApplication; 29 import org.openstreetmap.josm.gui.MapFrame; 30 import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils; 9 10 import javax.swing.ComboBoxEditor; 11 31 12 import org.openstreetmap.josm.gui.widgets.JosmComboBox; 32 import org.openstreetmap.josm.spi.preferences.Config;33 13 import org.openstreetmap.josm.tools.Logging; 34 14 … … 45 25 * @since 18173 46 26 */ 47 public class AutoCompComboBox<E> extends JosmComboBox<E> implements KeyListener { 48 49 /** a regex that matches numbers */ 50 private static final Pattern IS_NUMBER = Pattern.compile("^\\d+$"); 51 /** true if the combobox should autocomplete */ 52 private boolean autocompleteEnabled = true; 53 /** the editor will not accept text longer than this. -1 to disable */ 54 private int maxTextLength = -1; 27 public class AutoCompComboBox<E> extends JosmComboBox<E> implements AutoCompListener { 28 55 29 /** force a different keyboard input locale for the editor */ 56 30 private boolean useFixedLocale; 57 58 /** Whether to autocomplete numbers */59 private final boolean AUTOCOMPLETE_NUMBERS = !Config.getPref().getBoolean("autocomplete.dont_complete_numbers", true);60 61 31 private final transient InputContext privateInputContext = InputContext.getInstance(); 62 63 static final class InnerFocusListener implements FocusListener {64 private final JTextComponent editorComponent;65 66 InnerFocusListener(JTextComponent editorComponent) {67 this.editorComponent = editorComponent;68 }69 70 @Override71 public void focusLost(FocusEvent e) {72 MapFrame map = MainApplication.getMap();73 if (map != null) {74 map.keyDetector.setEnabled(true);75 }76 }77 78 @Override79 public void focusGained(FocusEvent e) {80 MapFrame map = MainApplication.getMap();81 if (map != null) {82 map.keyDetector.setEnabled(false);83 }84 // save unix system selection (middle mouse paste)85 Clipboard sysSel = ClipboardUtils.getSystemSelection();86 if (sysSel != null) {87 Transferable old = ClipboardUtils.getClipboardContent(sysSel);88 editorComponent.selectAll();89 if (old != null) {90 sysSel.setContents(old, null);91 }92 } else if (e != null && e.getOppositeComponent() != null) {93 // Select all characters when the change of focus occurs inside JOSM only.94 // When switching from another application, it is annoying, see #1374795 editorComponent.selectAll();96 }97 }98 }99 100 /**101 * A {@link DocumentFilter} to limit the text length in the editor.102 */103 private class MaxLengthDocumentFilter extends DocumentFilter {104 @Override105 public void insertString(FilterBypass fb, int offset, String string, AttributeSet attr)106 throws BadLocationException {107 if (mustInsertOrReplace(fb, 0, string, attr)) {108 super.insertString(fb, offset, string, attr);109 }110 }111 112 @Override113 public void replace(FilterBypass fb, int offset, int length, String string, AttributeSet attr)114 throws BadLocationException {115 if (mustInsertOrReplace(fb, length, string, attr)) {116 super.replace(fb, offset, length, string, attr);117 }118 }119 120 private boolean mustInsertOrReplace(FilterBypass fb, int length, String string, AttributeSet attr) {121 int newLen = fb.getDocument().getLength() - length + ((string == null) ? 0 : string.length());122 return (maxTextLength == -1 || newLen <= maxTextLength ||123 // allow longer text while composing characters or it will be hard to compose124 // the last characters before the limit125 ((attr != null) && attr.isDefined(StyleConstants.ComposedTextAttribute)));126 }127 }128 32 129 33 /** … … 141 45 public AutoCompComboBox(AutoCompComboBoxModel<E> model) { 142 46 super(model); 143 Objects.requireNonNull(model, "A model cannot be null.");47 setEditor(new AutoCompComboBoxEditor<E>()); 144 48 setEditable(true); 145 final JTextComponent editorComponent = getEditorComponent(); 146 editorComponent.addFocusListener(new InnerFocusListener(editorComponent)); 147 editorComponent.addKeyListener(this); 148 ((AbstractDocument) editorComponent.getDocument()).setDocumentFilter(new MaxLengthDocumentFilter()); 49 getEditorComponent().setModel(model); 50 getEditorComponent().addAutoCompListener(this); 149 51 } 150 52 … … 152 54 * Returns the {@link AutoCompComboBoxModel} currently used. 153 55 * 154 * @return the model 56 * @return the model or null 155 57 */ 156 58 @Override … … 159 61 } 160 62 161 /** 162 * Autocompletes what the user typed in. 163 * <p> 164 * Gets the user input from the editor, finds the best matching item in the model, selects it in 165 * the list, sets the editor text, and highlights the autocompleted part. If there is no 166 * matching item, removes the list selection. 167 */ 168 private void autocomplete() { 169 JTextField editor = getEditorComponent(); 170 String prefix = editor.getText(); 171 if (!AUTOCOMPLETE_NUMBERS && IS_NUMBER.matcher(prefix).matches()) 172 return; 173 174 E item = getModel().findBestCandidate(prefix); 175 if (item != null) { 176 String text = item.toString(); 177 // This calls setItem() if the selected item changed 178 // See: javax.swing.plaf.basic.BasicComboBoxUI.Handler.contentsChanged(ListDataEvent e) 179 setSelectedItem(item); 180 // set manually in case the selected item didn't change 181 editor.setText(text); 182 // select the autocompleted suffix in the editor 183 editor.select(prefix.length(), text.length()); 184 // copy the whole autocompleted string to the unix system-wide selection (aka 185 // middle-click), else only the selected suffix would be copied 186 copyToSysSel(text); 187 } else { 188 setSelectedItem(null); 189 // avoid setItem because it selects the whole text (on windows only) 190 editor.setText(prefix); 191 } 192 } 193 194 /** 195 * Copies a String to the UNIX system-wide selection (aka middle-click). 196 * 197 * @param s the string to copy 198 */ 199 void copyToSysSel(String s) { 200 Clipboard sysSel = ClipboardUtils.getSystemSelection(); 201 if (sysSel != null) { 202 Transferable transferable = new StringSelection(s); 203 sysSel.setContents(transferable, null); 204 } 205 } 206 207 /** 208 * Sets the maximum text length. 209 * 210 * @param length the maximum text length in number of characters 211 */ 212 public void setMaxTextLength(int length) { 213 maxTextLength = length; 63 @Override 64 public void setEditor(ComboBoxEditor newEditor) { 65 if (editor != null) { 66 editor.getEditorComponent().removePropertyChangeListener(this); 67 } 68 super.setEditor(newEditor); 69 if (editor != null) { 70 // listen to orientation changes in the editor 71 editor.getEditorComponent().addPropertyChangeListener(this); 72 } 73 } 74 75 /** 76 * Returns the editor component 77 * 78 * @return the editor component 79 * @see ComboBoxEditor#getEditorComponent() 80 * @since 18221 81 */ 82 @Override 83 @SuppressWarnings("unchecked") 84 public AutoCompTextField<E> getEditorComponent() { 85 return getEditor() == null ? null : (AutoCompTextField<E>) getEditor().getEditorComponent(); 86 } 87 88 /** 89 * Selects the autocompleted item in the dropdown. 90 * 91 * @param item the item selected for autocomplete 92 */ 93 private void autocomplete(Object item) { 94 // Save the text in case item is null, because setSelectedItem will erase it. 95 String savedText = getText(); 96 setSelectedItem(item); 97 setText(savedText); 214 98 } 215 99 … … 263 147 264 148 /** 265 * Returns {@code true} if autocompletion is enabled.266 *267 * @return {@code true} if autocompletion is enabled.268 */269 public final boolean isAutocompleteEnabled() {270 return autocompleteEnabled;271 }272 273 /**274 149 * Enables or disables the autocompletion. 275 150 * … … 279 154 */ 280 155 public boolean setAutocompleteEnabled(boolean enabled) { 281 boolean oldEnabled = this.autocompleteEnabled; 282 this.autocompleteEnabled = enabled; 283 return oldEnabled; 156 return getEditorComponent().setAutocompleteEnabled(enabled); 284 157 } 285 158 … … 319 192 } 320 193 321 /* 322 * The KeyListener interface 323 */ 324 325 /** 326 * Listens to key events and eventually schedules an autocomplete. 327 * 328 * @param e the key event 329 */ 330 @Override 331 public void keyTyped(KeyEvent e) { 332 if (autocompleteEnabled 333 // and selection is at the end 334 && getEditorComponent().getSelectionEnd() == getEditorComponent().getText().length() 335 // and something visible was typed 336 && !Character.isISOControl(e.getKeyChar())) { 337 // We got the event before the editor component could see it. Let the editor do its job first. 338 SwingUtilities.invokeLater(() -> autocomplete()); 339 } 340 } 341 342 @Override 343 public void keyPressed(KeyEvent e) { 344 } 345 346 @Override 347 public void keyReleased(KeyEvent e) { 194 /** AutoCompListener Interface */ 195 196 @Override 197 public void autoCompBefore(AutoCompEvent e) { 198 } 199 200 @Override 201 public void autoCompPerformed(AutoCompEvent e) { 202 autocomplete(e.getItem()); 348 203 } 349 204 } -
trunk/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompComboBoxModel.java
r18173 r18221 2 2 package org.openstreetmap.josm.gui.tagging.ac; 3 3 4 import java.util.ArrayList;5 import java.util.Collection;6 4 import java.util.Comparator; 7 import java.util.Iterator;8 import java.util.List;9 5 import java.util.Objects; 10 import java.util.function.Function;11 6 12 import javax.swing.AbstractListModel; 13 import javax.swing.MutableComboBoxModel; 14 15 import org.openstreetmap.josm.data.preferences.ListProperty; 16 import org.openstreetmap.josm.spi.preferences.Config; 7 import org.openstreetmap.josm.gui.widgets.JosmComboBoxModel; 17 8 18 9 /** … … 23 14 * @since 18173 24 15 */ 25 public class AutoCompComboBoxModel<E> extends AbstractListModel<E> implements MutableComboBoxModel<E>, Iterable<E> {16 public class AutoCompComboBoxModel<E> extends JosmComboBoxModel<E> { 26 17 27 18 /** … … 33 24 */ 34 25 private Comparator<E> comparator; 35 /** The maximum number of elements to hold, -1 for no limit. Used for histories. */36 private int maxSize = -1;37 38 /** the elements shown in the dropdown */39 protected ArrayList<E> elements = new ArrayList<>();40 /** the selected element in the dropdown or null */41 protected Object selected;42 26 43 27 /** … … 74 58 75 59 /** 76 * Sets the maximum number of elements.77 *78 * @param size The maximal number of elements in the model.79 */80 public void setSize(int size) {81 maxSize = size;82 }83 84 /**85 * Returns a copy of the element list.86 * @return a copy of the data87 */88 public Collection<E> asCollection() {89 return new ArrayList<>(elements);90 }91 92 //93 // interface java.lang.Iterable94 //95 96 @Override97 public Iterator<E> iterator() {98 return elements.iterator();99 }100 101 //102 // interface javax.swing.MutableComboBoxModel103 //104 105 /**106 * Adds an element to the end of the model. Does nothing if max size is already reached.107 */108 @Override109 public void addElement(E element) {110 if (element != null && (maxSize == -1 || getSize() < maxSize)) {111 elements.add(element);112 }113 }114 115 @Override116 public void removeElement(Object elem) {117 elements.remove(elem);118 }119 120 @Override121 public void removeElementAt(int index) {122 Object elem = getElementAt(index);123 if (elem == selected) {124 if (index == 0) {125 setSelectedItem(getSize() == 1 ? null : getElementAt(index + 1));126 } else {127 setSelectedItem(getElementAt(index - 1));128 }129 }130 elements.remove(index);131 fireIntervalRemoved(this, index, index);132 }133 134 /**135 * Adds an element at a specific index.136 *137 * @param element The element to add138 * @param index Location to add the element139 */140 @Override141 public void insertElementAt(E element, int index) {142 if (maxSize != -1 && maxSize <= getSize()) {143 removeElementAt(getSize() - 1);144 }145 elements.add(index, element);146 }147 148 //149 // javax.swing.ComboBoxModel150 //151 152 /**153 * Set the value of the selected item. The selected item may be null.154 *155 * @param elem The combo box value or null for no selection.156 */157 @Override158 public void setSelectedItem(Object elem) {159 if ((selected != null && !selected.equals(elem)) ||160 (selected == null && elem != null)) {161 selected = elem;162 fireContentsChanged(this, -1, -1);163 }164 }165 166 @Override167 public Object getSelectedItem() {168 return selected;169 }170 171 //172 // javax.swing.ListModel173 //174 175 @Override176 public int getSize() {177 return elements.size();178 }179 180 @Override181 public E getElementAt(int index) {182 if (index >= 0 && index < elements.size())183 return elements.get(index);184 else185 return null;186 }187 188 //189 // end interfaces190 //191 192 /**193 * Adds all elements from the collection.194 *195 * @param elems The elements to add.196 */197 public void addAllElements(Collection<E> elems) {198 elems.forEach(e -> addElement(e));199 }200 201 /**202 * Adds all elements from the collection of string representations.203 *204 * @param strings The string representation of the elements to add.205 * @param buildE A {@link java.util.function.Function} that builds an {@code <E>} from a206 * {@code String}.207 */208 public void addAllElements(Collection<String> strings, Function<String, E> buildE) {209 strings.forEach(s -> addElement(buildE.apply(s)));210 }211 212 /**213 * Adds an element to the top of the list.214 * <p>215 * If the element is already in the model, moves it to the top. If the model gets too big,216 * deletes the last element.217 *218 * @param newElement the element to add219 * @return The element that is at the top now.220 */221 public E addTopElement(E newElement) {222 // if the element is already at the top, do nothing223 if (newElement.equals(getElementAt(0)))224 return getElementAt(0);225 226 removeElement(newElement);227 insertElementAt(newElement, 0);228 return newElement;229 }230 231 /**232 * Empties the list.233 */234 public void removeAllElements() {235 if (!elements.isEmpty()) {236 int firstIndex = 0;237 int lastIndex = elements.size() - 1;238 elements.clear();239 selected = null;240 fireIntervalRemoved(this, firstIndex, lastIndex);241 } else {242 selected = null;243 }244 }245 246 /**247 60 * Finds the best candidate for autocompletion. 248 61 * <p> … … 263 76 .orElse(null); 264 77 } 265 266 /**267 * Gets a preference loader and saver.268 *269 * @param readE A {@link Function} that builds an {@code <E>} from a {@link String}.270 * @param writeE A {@code Function} that serializes an {@code <E>} to a {@code String}271 * @return The {@link Preferences} instance.272 */273 public Preferences prefs(Function<String, E> readE, Function<E, String> writeE) {274 return new Preferences(readE, writeE);275 }276 277 /**278 * Loads and saves the model to the JOSM preferences.279 * <p>280 * Obtainable through {@link #prefs}.281 */282 public final class Preferences {283 284 /** A {@link Function} that builds an {@code <E>} from a {@code String}. */285 private Function<String, E> readE;286 /** A {@code Function} that serializes {@code <E>} to a {@code String}. */287 private Function<E, String> writeE;288 289 /**290 * Private constructor291 *292 * @param readE A {@link Function} that builds an {@code <E>} from a {@code String}.293 * @param writeE A {@code Function} that serializes an {@code <E>} to a {@code String}294 */295 private Preferences(Function<String, E> readE, Function<E, String> writeE) {296 this.readE = readE;297 this.writeE = writeE;298 }299 300 /**301 * Loads the model from the JOSM preferences.302 * @param key The preferences key303 */304 public void load(String key) {305 removeAllElements();306 addAllElements(Config.getPref().getList(key), readE);307 }308 309 /**310 * Loads the model from the JOSM preferences.311 *312 * @param key The preferences key313 * @param defaults A list of default values.314 */315 public void load(String key, List<String> defaults) {316 removeAllElements();317 addAllElements(Config.getPref().getList(key, defaults), readE);318 }319 320 /**321 * Loads the model from the JOSM preferences.322 *323 * @param prop The property holding the strings.324 */325 public void load(ListProperty prop) {326 removeAllElements();327 addAllElements(prop.get(), readE);328 }329 330 /**331 * Returns the model elements as list of strings.332 *333 * @return a list of strings334 */335 public List<String> asStringList() {336 List<String> list = new ArrayList<>(getSize());337 forEach(element -> list.add(writeE.apply(element)));338 return list;339 }340 341 /**342 * Saves the model to the JOSM preferences.343 *344 * @param key The preferences key345 */346 public void save(String key) {347 Config.getPref().putList(key, asStringList());348 }349 350 /**351 * Saves the model to the JOSM preferences.352 *353 * @param prop The property to write to.354 */355 public void save(ListProperty prop) {356 prop.put(asStringList());357 }358 }359 78 } -
trunk/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompletionManager.java
r18211 r18221 346 346 347 347 /** 348 * Returns all cached {@link AutoCompletionItem}s for given keys. 349 * 350 * @param keys retrieve the items for these keys 351 * @return the currently cached items, sorted by priority and alphabet 352 * @since 18221 353 */ 354 public List<AutoCompletionItem> getAllForKeys(List<String> keys) { 355 Map<String, AutoCompletionPriority> map = new HashMap<>(); 356 357 for (String key : keys) { 358 for (String value : TaggingPresets.getPresetValues(key)) { 359 map.merge(value, AutoCompletionPriority.IS_IN_STANDARD, AutoCompletionPriority::mergeWith); 360 } 361 for (String value : getDataValues(key)) { 362 map.merge(value, AutoCompletionPriority.IS_IN_DATASET, AutoCompletionPriority::mergeWith); 363 } 364 for (String value : getUserInputValues(key)) { 365 map.merge(value, AutoCompletionPriority.UNKNOWN, AutoCompletionPriority::mergeWith); 366 } 367 } 368 return map.entrySet().stream().map(e -> new AutoCompletionItem(e.getKey(), e.getValue())).sorted().collect(Collectors.toList()); 369 } 370 371 /** 348 372 * Returns the currently cached tag keys. 349 373 * @return a set of tag keys -
trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPreset.java
r18208 r18221 7 7 8 8 import java.awt.Component; 9 import java.awt.ComponentOrientation; 9 10 import java.awt.Dimension; 10 11 import java.awt.GridBagLayout; … … 16 17 import java.util.EnumSet; 17 18 import java.util.LinkedHashSet; 18 import java.util.LinkedList;19 19 import java.util.List; 20 20 import java.util.Map; … … 91 91 public class TaggingPreset extends AbstractAction implements ActiveLayerChangeListener, AdaptableAction, Predicate<IPrimitive> { 92 92 93 /** The user pressed the "Apply" button */ 93 94 public static final int DIALOG_ANSWER_APPLY = 1; 95 /** The user pressed the "New Relation" button */ 94 96 public static final int DIALOG_ANSWER_NEW_RELATION = 2; 97 /** The user pressed the "Cancel" button */ 95 98 public static final int DIALOG_ANSWER_CANCEL = 3; 96 99 100 /** The action key for optional tooltips */ 97 101 public static final String OPTIONAL_TOOLTIP_TEXT = "Optional tooltip text"; 98 102 … … 120 124 */ 121 125 public String iconName; 126 /** 127 * Translation context for name 128 */ 122 129 public String name_context; 123 130 /** … … 126 133 */ 127 134 public String locale_name; 135 /** 136 * Show the preset name if true 137 */ 128 138 public boolean preset_name_label; 129 139 … … 132 142 */ 133 143 public transient Set<TaggingPresetType> types; 144 /** 145 * The list of preset items 146 */ 134 147 public final transient List<TaggingPresetItem> data = new ArrayList<>(2); 148 /** 149 * The roles for this relation (if we are editing a relation). See: 150 * <a href="https://josm.openstreetmap.de/wiki/TaggingPresets#Tags">JOSM wiki</a> 151 */ 135 152 public transient Roles roles; 153 /** 154 * The name_template custom name formatter. See: 155 * <a href="https://josm.openstreetmap.de/wiki/TaggingPresets#Attributes">JOSM wiki</a> 156 */ 136 157 public transient TemplateEntry nameTemplate; 158 /** The name_template_filter */ 137 159 public transient Match nameTemplateFilter; 160 /** The match_expression */ 138 161 public transient Match matchExpression; 139 162 … … 145 168 /** The completable future task of asynchronous icon loading */ 146 169 private CompletableFuture<Void> iconFuture; 170 171 /** Support functions */ 172 protected TaggingPresetItemGuiSupport itemGuiSupport; 147 173 148 174 /** … … 277 303 } 278 304 279 public void setName_template(String pattern) throws SAXException { 305 /** 306 * Sets the name_template custom name formatter. 307 * 308 * @param template The format template 309 * @throws SAXException on template parse error 310 * @see <a href="https://josm.openstreetmap.de/wiki/TaggingPresets#name_templatedetails">JOSM wiki</a> 311 */ 312 public void setName_template(String template) throws SAXException { 280 313 try { 281 this.nameTemplate = new TemplateParser( pattern).parse();314 this.nameTemplate = new TemplateParser(template).parse(); 282 315 } catch (ParseError e) { 283 Logging.error("Error while parsing " + pattern+ ": " + e.getMessage());316 Logging.error("Error while parsing " + template + ": " + e.getMessage()); 284 317 throw new SAXException(e); 285 318 } 286 319 } 287 320 321 /** 322 * Sets the name_template_filter. 323 * 324 * @param filter The search pattern 325 * @throws SAXException on search patern parse error 326 * @see <a href="https://josm.openstreetmap.de/wiki/TaggingPresets#name_templatedetails">JOSM wiki</a> 327 */ 288 328 public void setName_template_filter(String filter) throws SAXException { 289 329 try { … … 295 335 } 296 336 337 /** 338 * Sets the match_expression additional criteria for matching primitives. 339 * 340 * @param filter The search pattern 341 * @throws SAXException on search patern parse error 342 * @see <a href="https://josm.openstreetmap.de/wiki/TaggingPresets#Attributes">JOSM wiki</a> 343 */ 297 344 public void setMatch_expression(String filter) throws SAXException { 298 345 try { … … 321 368 public PresetPanel createPanel(Collection<OsmPrimitive> selected) { 322 369 PresetPanel p = new PresetPanel(); 323 List<Link> l = new LinkedList<>();324 370 325 371 final JPanel pp = new JPanel(); … … 355 401 356 402 boolean presetInitiallyMatches = !selected.isEmpty() && selected.stream().allMatch(this); 357 final TaggingPresetItemGuiSupport itemGuiSupport = TaggingPresetItemGuiSupport.create( 358 presetInitiallyMatches, selected, this::getChangedTags); 359 JPanel items = new JPanel(new GridBagLayout()); 403 itemGuiSupport = TaggingPresetItemGuiSupport.create(presetInitiallyMatches, selected, this::getChangedTags); 404 405 JPanel itemPanel = new JPanel(new GridBagLayout()) { 406 /** 407 * This hack allows the items to have their own orientation. 408 * 409 * The problem is that 410 * {@link org.openstreetmap.josm.gui.ExtendedDialog#showDialog ExtendedDialog} calls 411 * {@code applyComponentOrientation} very late in the dialog construction process thus 412 * overwriting the orientation the components have chosen for themselves. 413 * 414 * This stops the propagation of {@code applyComponentOrientation}, thus all 415 * {@code TaggingPresetItem}s may (and have to) set their own orientation. 416 */ 417 @Override 418 public void applyComponentOrientation(ComponentOrientation o) { 419 setComponentOrientation(o); 420 } 421 }; 422 JPanel linkPanel = new JPanel(new GridBagLayout()); 360 423 TaggingPresetItem previous = null; 361 424 for (TaggingPresetItem i : data) { 362 425 if (i instanceof Link) { 363 l.add((Link) i);426 i.addToPanel(linkPanel, itemGuiSupport); 364 427 p.hasElements = true; 365 428 } else { … … 367 430 PresetLink link = (PresetLink) i; 368 431 if (!(previous instanceof PresetLink && Objects.equals(((PresetLink) previous).text, link.text))) { 369 item s.add(link.createLabel(), GBC.eol().insets(0, 8, 0, 0));432 itemPanel.add(link.createLabel(), GBC.eol().insets(0, 8, 0, 0)); 370 433 } 371 434 } 372 if (i.addToPanel(item s, itemGuiSupport)) {435 if (i.addToPanel(itemPanel, itemGuiSupport)) { 373 436 p.hasElements = true; 374 437 } … … 376 439 previous = i; 377 440 } 378 p.add(items, GBC.eol().fill()); 441 p.add(itemPanel, GBC.eol().fill()); 442 p.add(linkPanel, GBC.eol().fill()); 443 379 444 if (selected.isEmpty() && !supportsRelation()) { 380 GuiHelper.setEnabledRec(item s, false);445 GuiHelper.setEnabledRec(itemPanel, false); 381 446 } 382 447 … … 384 449 itemGuiSupport.addListener((source, key, newValue) -> 385 450 TaggingPresetValidation.validateAsync(selected.iterator().next(), validationLabel, getChangedTags())); 386 }387 388 // add Link389 for (Link link : l) {390 link.addToPanel(p, itemGuiSupport);391 451 } 392 452 … … 396 456 p.add(tb, GBC.std(1, 0).anchor(GBC.LINE_END)); 397 457 398 // Trigger initial updates 458 // Trigger initial updates once and only once 459 itemGuiSupport.setEnabled(true); 399 460 itemGuiSupport.fireItemValueModified(null, null, null); 461 400 462 return p; 401 463 } … … 410 472 } 411 473 474 /** 475 * Suggests a relation role for this primitive 476 * 477 * @param osm The primitive 478 * @return the suggested role or null 479 */ 412 480 public String suggestRoleForOsmPrimitive(OsmPrimitive osm) { 413 481 if (roles != null && osm != null) { -
trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetItem.java
r18208 r18221 8 8 import java.util.Arrays; 9 9 import java.util.Collection; 10 import java.util.Collections; 10 11 import java.util.EnumSet; 11 12 import java.util.List; … … 21 22 import org.openstreetmap.josm.data.osm.Tag; 22 23 import org.openstreetmap.josm.data.preferences.BooleanProperty; 24 import org.openstreetmap.josm.data.tagging.ac.AutoCompletionItem; 23 25 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField; 24 26 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionList; … … 55 57 AutoCompletionManager.of(data).populateWithTagValues(list, keys); 56 58 field.setAutoCompletionList(list); 59 } 60 61 /** 62 * Returns all cached {@link AutoCompletionItem}s for given keys. 63 * 64 * @param keys retrieve the items for these keys 65 * @return the currently cached items, sorted by priority and alphabet 66 * @since 18221 67 */ 68 protected List<AutoCompletionItem> getAllForKeys(List<String> keys) { 69 DataSet data = OsmDataManager.getInstance().getEditDataSet(); 70 if (data == null) { 71 return Collections.emptyList(); 72 } 73 return AutoCompletionManager.of(data).getAllForKeys(keys); 57 74 } 58 75 -
trunk/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetItemGuiSupport.java
r18208 r18221 2 2 package org.openstreetmap.josm.gui.tagging.presets; 3 3 4 import java.awt.ComponentOrientation; 4 5 import java.util.Arrays; 5 6 import java.util.Collection; … … 11 12 import org.openstreetmap.josm.data.osm.Tagged; 12 13 import org.openstreetmap.josm.data.osm.search.SearchCompiler; 14 import org.openstreetmap.josm.gui.widgets.OrientationAction; 13 15 import org.openstreetmap.josm.tools.ListenerList; 14 16 import org.openstreetmap.josm.tools.Utils; … … 26 28 private final Supplier<Collection<Tag>> changedTagsSupplier; 27 29 private final ListenerList<ChangeListener> listeners = ListenerList.create(); 30 31 /** whether to fire events or not */ 32 private boolean enabled = false; 33 34 /** 35 * Returns whether firing of events is enabled 36 * 37 * @return true if firing of events is enabled 38 */ 39 public boolean isEnabled() { 40 return enabled; 41 } 42 43 /** 44 * Enables or disables the firing of events 45 * 46 * @param enabled fires if true 47 * @return the old state of enabled 48 */ 49 public boolean setEnabled(boolean enabled) { 50 boolean oldEnabled = this.enabled; 51 this.enabled = enabled; 52 return oldEnabled; 53 } 28 54 29 55 /** … … 120 146 } 121 147 148 /** 149 * Returns the default component orientation by the user's locale 150 * 151 * @return the default component orientation 152 */ 153 public ComponentOrientation getDefaultComponentOrientation() { 154 return OrientationAction.getDefaultComponentOrientation(); 155 } 156 122 157 @Override 123 158 public boolean evaluateCondition(SearchCompiler.Match condition) { … … 140 175 */ 141 176 public void fireItemValueModified(TaggingPresetItem source, String key, String newValue) { 142 listeners.fireEvent(e -> e.itemValueModified(source, key, newValue)); 177 if (enabled) 178 listeners.fireEvent(e -> e.itemValueModified(source, key, newValue)); 143 179 } 144 180 } -
trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Check.java
r18077 r18221 86 86 if (icon != null) { 87 87 JPanel checkPanel = IconTextCheckBox.wrap(check, locale_text, getIcon()); 88 checkPanel.applyComponentOrientation(support.getDefaultComponentOrientation()); 88 89 p.add(checkPanel, GBC.eol()); // Do not fill, see #15104 89 90 } else { 91 check.applyComponentOrientation(support.getDefaultComponentOrientation()); 90 92 p.add(check, GBC.eol()); // Do not fill, see #15104 91 93 } -
trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/CheckGroup.java
r17829 r18221 46 46 } 47 47 48 panel.applyComponentOrientation(support.getDefaultComponentOrientation()); 48 49 p.add(panel, GBC.eol()); 49 50 return false; -
trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Combo.java
r17651 r18221 6 6 import java.awt.Color; 7 7 import java.awt.Cursor; 8 import java.awt.Insets; 8 9 import java.awt.event.ActionEvent; 9 10 import java.awt.event.ActionListener; 11 import java.awt.event.ComponentAdapter; 12 import java.awt.event.ComponentEvent; 13 import java.util.Arrays; 14 import java.util.Comparator; 10 15 11 16 import javax.swing.AbstractAction; 12 17 import javax.swing.JButton; 13 18 import javax.swing.JColorChooser; 19 import javax.swing.JComponent; 14 20 import javax.swing.JPanel; 15 21 22 import org.openstreetmap.josm.data.tagging.ac.AutoCompletionItem; 16 23 import org.openstreetmap.josm.data.tagging.ac.AutoCompletionPriority; 17 24 import org.openstreetmap.josm.gui.MainApplication; 18 25 import org.openstreetmap.josm.gui.mappaint.mapcss.CSSColors; 19 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField; 20 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionList; 26 import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBoxEditor; 27 import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBoxModel; 28 import org.openstreetmap.josm.gui.tagging.ac.AutoCompTextField; 21 29 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItem; 22 30 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport; … … 36 44 public boolean editable = true; // NOSONAR 37 45 /** The length of the combo box (number of characters allowed). */ 38 public short length; // NOSONAR46 public int length; // NOSONAR 39 47 40 48 protected JosmComboBox<PresetListEntry> combobox; 49 protected AutoCompComboBoxModel<PresetListEntry> dropDownModel; 50 protected AutoCompComboBoxModel<AutoCompletionItem> autoCompModel; 51 52 class ComponentListener extends ComponentAdapter { 53 @Override 54 public void componentResized(ComponentEvent e) { 55 // Make multi-line JLabels the correct size 56 // Only needed if there is any short_description 57 JComponent component = (JComponent) e.getSource(); 58 int width = component.getWidth(); 59 if (width == 0) 60 width = 200; 61 Insets insets = component.getInsets(); 62 width -= insets.left + insets.right + 10; 63 ComboMultiSelectListCellRenderer renderer = (ComboMultiSelectListCellRenderer) combobox.getRenderer(); 64 renderer.setWidth(width); 65 combobox.setRenderer(null); // needed to make prop change fire 66 combobox.setRenderer(renderer); 67 } 68 } 41 69 42 70 /** … … 48 76 49 77 @Override 50 protected voidaddToPanelAnchor(JPanel p, String def, TaggingPresetItemGuiSupport support) {78 protected JComponent addToPanelAnchor(JPanel p, String def, TaggingPresetItemGuiSupport support) { 51 79 if (!usage.unused()) { 52 80 for (String s : usage.values) { … … 59 87 presetListEntries.add(new PresetListEntry("")); 60 88 61 combobox = new JosmComboBox<>(presetListEntries.toArray(new PresetListEntry[0])); 62 component = combobox; 63 combobox.setRenderer(getListCellRenderer()); 64 combobox.setEditable(true); // fix incorrect height, see #6157 65 combobox.reinitialize(presetListEntries); 66 combobox.setEditable(editable); // see #6157 67 AutoCompletingTextField tf = new AutoCompletingTextField(); 68 initAutoCompletionField(tf, key); 89 dropDownModel = new AutoCompComboBoxModel<PresetListEntry>(Comparator.naturalOrder()); 90 autoCompModel = new AutoCompComboBoxModel<AutoCompletionItem>(Comparator.naturalOrder()); 91 presetListEntries.forEach(dropDownModel::addElement); 92 93 combobox = new JosmComboBox<>(dropDownModel); 94 AutoCompComboBoxEditor<AutoCompletionItem> editor = new AutoCompComboBoxEditor<>(); 95 combobox.setEditor(editor); 96 97 // The default behaviour of JComboBox is to size the editor according to the tallest item in 98 // the dropdown list. We don't want that to happen because we want to show taller items in 99 // the list than in the editor. We can't use 100 // {@code combobox.setPrototypeDisplayValue(new PresetListEntry(" "));} because that would 101 // set a fixed cell height in JList. 102 combobox.setPreferredHeight(combobox.getPreferredSize().height); 103 104 // a custom cell renderer capable of displaying a short description text along with the 105 // value 106 combobox.setRenderer(new ComboMultiSelectListCellRenderer(combobox, combobox.getRenderer(), 200, key)); 107 combobox.setEditable(editable); 108 109 getAllForKeys(Arrays.asList(key)).forEach(autoCompModel::addElement); 110 getDisplayValues().forEach(s -> autoCompModel.addElement(new AutoCompletionItem(s, AutoCompletionPriority.IS_IN_STANDARD))); 111 112 AutoCompTextField<AutoCompletionItem> tf = editor.getEditorComponent(); 113 tf.setModel(autoCompModel); 114 69 115 if (TaggingPresetItem.DISPLAY_KEYS_AS_HINT.get()) { 70 tf.setHint(key);116 combobox.setHint(key); 71 117 } 72 118 if (length > 0) { 73 tf.setMax Chars((int)length);119 tf.setMaxTextLength(length); 74 120 } 75 AutoCompletionList acList = tf.getAutoCompletionList();76 if (acList != null) {77 acList.add(getDisplayValues(), AutoCompletionPriority.IS_IN_STANDARD);78 }79 combobox.setEditor(tf);80 combobox.setSelectedItem(getItemToSelect(def, support, false));81 121 82 122 if (key != null && ("colour".equals(key) || key.startsWith("colour:") || key.endsWith(":colour"))) { … … 93 133 p.add(combobox, GBC.eol().fill(GBC.HORIZONTAL)); 94 134 } 135 136 Object itemToSelect = getItemToSelect(default_, support, false); 137 combobox.setSelectedItemText(itemToSelect == null ? null : itemToSelect.toString()); 95 138 combobox.addActionListener(l -> support.fireItemValueModified(this, key, getSelectedValue())); 139 combobox.addComponentListener(new ComponentListener()); 140 return combobox; 96 141 } 97 142 -
trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/ComboMultiSelect.java
r17919 r18221 6 6 7 7 import java.awt.Component; 8 import java.awt.Dimension;9 import java.awt.Font;10 8 import java.lang.reflect.Method; 11 9 import java.lang.reflect.Modifier; … … 14 12 import java.util.Collection; 15 13 import java.util.Collections; 16 import java.util.Comparator;17 14 import java.util.List; 18 15 import java.util.Objects; … … 20 17 import java.util.concurrent.CopyOnWriteArraySet; 21 18 import java.util.stream.Collectors; 22 import java.util.stream.IntStream;23 19 24 20 import javax.swing.JComponent; … … 32 28 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetSelector; 33 29 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets; 30 import org.openstreetmap.josm.gui.widgets.JosmListCellRenderer; 31 import org.openstreetmap.josm.gui.widgets.OrientationAction; 34 32 import org.openstreetmap.josm.tools.GBC; 35 33 import org.openstreetmap.josm.tools.Logging; … … 40 38 */ 41 39 public abstract class ComboMultiSelect extends KeyedItem { 42 43 private static final Renderer RENDERER = new Renderer();44 40 45 41 /** … … 93 89 public boolean values_searchable; // NOSONAR 94 90 95 protected JComponent component;96 91 protected final Set<PresetListEntry> presetListEntries = new CopyOnWriteArraySet<>(); 97 92 private boolean initialized; … … 99 94 protected Object originalValue; 100 95 101 private static final class Renderer implements ListCellRenderer<PresetListEntry> { 102 103 private final JLabel lbl = new JLabel(); 96 /** 97 * A list cell renderer that paints a short text in the current value pane and and a longer text 98 * in the dropdown list. 99 */ 100 static class ComboMultiSelectListCellRenderer extends JosmListCellRenderer<PresetListEntry> { 101 int width; 102 private String key; 103 104 ComboMultiSelectListCellRenderer(Component component, ListCellRenderer<? super PresetListEntry> renderer, int width, String key) { 105 super(component, renderer); 106 this.key = key; 107 setWidth(width); 108 } 109 110 /** 111 * Sets the width to format the dropdown list to 112 * 113 * Note: This is not the width of the list, but the width to which we format any multi-line 114 * label in the list. We cannot use the list's width because at the time the combobox 115 * measures its items, it is not guaranteed that the list is already sized, the combobox may 116 * not even be layed out yet. Set this to {@code combobox.getWidth()} 117 * 118 * @param width the width 119 */ 120 public void setWidth(int width) { 121 if (width <= 0) 122 width = 200; 123 this.width = width - 20; 124 } 104 125 105 126 @Override 106 public Component getListCellRendererComponent(JList<? extends PresetListEntry> list, PresetListEntry item, int index, 107 boolean isSelected, boolean cellHasFocus) { 108 109 if (list == null || item == null) { 110 return lbl; 111 } 112 113 if (index == -1) { 114 // Take the longest element for the preferred width (#19321) 115 // We do not want the editor to have the maximum height of all entries. Return a dummy with bogus height. 116 IntStream.range(0, list.getModel().getSize()) 117 .mapToObj(i -> getListCellRendererComponent(list, list.getModel().getElementAt(i), i, isSelected, cellHasFocus)) 118 .map(Component::getPreferredSize) 119 .max(Comparator.comparingInt(dim -> dim.width)) 120 .ifPresent(dim -> lbl.setPreferredSize(new Dimension(dim.width, 10))); 121 return lbl; 122 } 123 124 // Only return cached size, item is not shown 125 if (!list.isShowing() && item.preferredWidth != -1 && item.preferredHeight != -1) { 126 lbl.setPreferredSize(new Dimension(item.preferredWidth, item.preferredHeight)); 127 return lbl; 128 } 129 130 lbl.setPreferredSize(null); 131 132 if (isSelected) { 133 lbl.setBackground(list.getSelectionBackground()); 134 lbl.setForeground(list.getSelectionForeground()); 127 public JLabel getListCellRendererComponent( 128 JList<? extends PresetListEntry> list, PresetListEntry value, int index, boolean isSelected, boolean cellHasFocus) { 129 130 JLabel l = (JLabel) renderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); 131 if (index != -1) { 132 // index -1 is set when measuring the size of the cell and when painting the 133 // editor-ersatz of a readonly combobox. fixes #6157 134 l.setText(value.getListDisplay(width)); 135 } 136 String tt = value.value; 137 if (tt != null && !tt.isEmpty()) { 138 l.setToolTipText(tr("Sets the key ''{0}'' to the value ''{1}''.", key, tt)); 135 139 } else { 136 lbl.setBackground(list.getBackground()); 137 lbl.setForeground(list.getForeground()); 138 } 139 140 lbl.setOpaque(true); 141 lbl.setFont(lbl.getFont().deriveFont(Font.PLAIN)); 142 lbl.setText("<html>" + item.getListDisplay() + "</html>"); 143 lbl.setIcon(item.getIcon()); 144 lbl.setEnabled(list.isEnabled()); 145 146 // Cache size 147 item.preferredWidth = (short) lbl.getPreferredSize().width; 148 item.preferredHeight = (short) lbl.getPreferredSize().height; 149 150 return lbl; 140 l.setToolTipText(tr("Clears the key ''{0}''.", key)); 141 } 142 return l; 151 143 } 152 144 } … … 188 180 protected abstract Object getSelectedItem(); 189 181 190 protected abstract voidaddToPanelAnchor(JPanel p, String def, TaggingPresetItemGuiSupport support);182 protected abstract JComponent addToPanelAnchor(JPanel p, String def, TaggingPresetItemGuiSupport support); 191 183 192 184 @Override … … 219 211 label.setToolTipText(getKeyTooltipText()); 220 212 label.setComponentPopupMenu(getPopupMenu()); 213 label.applyComponentOrientation(OrientationAction.getDefaultComponentOrientation()); 221 214 p.add(label, GBC.std().insets(0, 0, 10, 0)); 222 addToPanelAnchor(p, default_, support);215 JComponent component = addToPanelAnchor(p, default_, support); 223 216 label.setLabelFor(component); 224 217 component.setToolTipText(getKeyTooltipText()); 218 component.applyComponentOrientation(OrientationAction.getValueOrientation(key)); 225 219 226 220 return true; … … 455 449 } 456 450 457 protected ListCellRenderer<PresetListEntry> getListCellRenderer() {458 return RENDERER;459 }460 461 451 @Override 462 452 public MatchType getDefaultMatch() { -
trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Label.java
r17609 r18221 18 18 JLabel label = new JLabel(locale_text); 19 19 addIcon(label); 20 label.applyComponentOrientation(support.getDefaultComponentOrientation()); 20 21 p.add(label, GBC.eol().fill(GBC.HORIZONTAL)); 21 22 return true; -
trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Link.java
r17609 r18221 36 36 public boolean addToPanel(JPanel p, TaggingPresetItemGuiSupport support) { 37 37 initializeLocaleText(tr("More information about this feature")); 38 Optional.ofNullable(buildUrlLabel()).ifPresent(label -> p.add(label, GBC.eol().insets(0, 10, 0, 0).fill(GBC.HORIZONTAL))); 38 UrlLabel label = buildUrlLabel(); 39 if (label != null) { 40 label.applyComponentOrientation(support.getDefaultComponentOrientation()); 41 p.add(label, GBC.eol().insets(0, 10, 0, 0).fill(GBC.HORIZONTAL)); 42 } 39 43 return false; 40 44 } -
trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/MultiSelect.java
r17639 r18221 8 8 import java.util.TreeSet; 9 9 10 import javax.swing.JComponent; 10 11 import javax.swing.JList; 11 12 import javax.swing.JPanel; 12 13 import javax.swing.JScrollPane; 13 import javax.swing.ListCellRenderer;14 14 import javax.swing.ListModel; 15 15 … … 31 31 32 32 @Override 33 protected voidaddToPanelAnchor(JPanel p, String def, TaggingPresetItemGuiSupport support) {33 protected JComponent addToPanelAnchor(JPanel p, String def, TaggingPresetItemGuiSupport support) { 34 34 list = new ConcatenatingJList(delimiter, presetListEntries.toArray(new PresetListEntry[0])); 35 component = list; 36 ListCellRenderer<PresetListEntry> renderer = getListCellRenderer(); 35 ComboMultiSelectListCellRenderer renderer = new ComboMultiSelectListCellRenderer(list, list.getCellRenderer(), 200, key); 37 36 list.setCellRenderer(renderer); 38 list.setSelectedItem(getItemToSelect(def, support, true)); 37 Object itemToSelect = getItemToSelect(def, support, true); 38 list.setSelectedItem(itemToSelect == null ? null : new PresetListEntry(itemToSelect.toString())); 39 39 JScrollPane sp = new JScrollPane(list); 40 40 // if a number of rows has been specified in the preset, … … 47 47 list.addListSelectionListener(l -> support.fireItemValueModified(this, key, getSelectedValue())); 48 48 p.add(sp, GBC.eol().fill(GBC.HORIZONTAL)); 49 return list; 49 50 } 50 51 -
trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Optional.java
r17609 r18221 19 19 @Override 20 20 public boolean addToPanel(JPanel p, TaggingPresetItemGuiSupport support) { 21 JLabel label = new JLabel(locale_text); 22 label.applyComponentOrientation(support.getDefaultComponentOrientation()); 21 23 initializeLocaleText(tr("Optional Attributes:")); 22 24 p.add(new JLabel(" "), GBC.eol()); // space 23 p.add( new JLabel(locale_text), GBC.eol());25 p.add(label, GBC.eol()); 24 26 p.add(new JLabel(" "), GBC.eol()); // space 25 27 return false; -
trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/PresetLink.java
r18170 r18221 62 62 JLabel lbl = new TaggingPresetLabel(t); 63 63 lbl.addMouseListener(new TaggingPresetMouseAdapter(t, support.getSelected())); 64 lbl.applyComponentOrientation(support.getDefaultComponentOrientation()); 64 65 p.add(lbl, GBC.eol().fill(GBC.HORIZONTAL)); 65 66 } -
trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/PresetListEntry.java
r18208 r18221 36 36 public String locale_short_description; // NOSONAR 37 37 38 /** Cached width (currently only for Combo) to speed up preset dialog initialization */39 public short preferredWidth = -1; // NOSONAR40 /** Cached height (currently only for Combo) to speed up preset dialog initialization */41 public short preferredHeight = -1; // NOSONAR42 43 38 /** 44 39 * Constructs a new {@code PresetListEntry}, uninitialized. … … 57 52 58 53 /** 59 * Returns HTML formatted contents. 54 * Returns the contents displayed in the dropdown list. 55 * 56 * This is the contents that would be displayed in the current view plus a short description to 57 * aid the user. The whole contents is wrapped to {@code width}. 58 * 59 * @param width the width in px 60 60 * @return HTML formatted contents 61 61 */ 62 public String getListDisplay() { 63 if (value.equals(KeyedItem.DIFFERENT)) 64 return "<b>" + Utils.escapeReservedCharactersHTML(KeyedItem.DIFFERENT) + "</b>"; 62 public String getListDisplay(int width) { 63 if (value.equals(KeyedItem.DIFFERENT)) { 64 return "<b>" + KeyedItem.DIFFERENT + "</b>"; 65 } 65 66 66 String displayValue = Utils.escapeReservedCharactersHTML(getDisplayValue());67 67 String shortDescription = getShortDescription(true); 68 String displayValue = getDisplayValue(); 68 69 69 if (displayValue.isEmpty() && Utils.isEmpty(shortDescription)) 70 return " "; 70 if (shortDescription.isEmpty()) { 71 if (displayValue.isEmpty()) { 72 return " "; 73 } 74 return displayValue; 75 } 71 76 72 final StringBuilder res = new StringBuilder("<b>").append(displayValue).append("</b>"); 73 if (!Utils.isEmpty(shortDescription)) { 74 // wrap in table to restrict the text width 75 res.append("<div style=\"width:300px; padding:0 0 5px 5px\">") 76 .append(shortDescription) 77 .append("</div>"); 78 } 79 return res.toString(); 77 // RTL not supported in HTML. See: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4866977 78 return String.format("<html><div style=\"width: %d\"><b>%s</b><p style=\"padding-left: 10\">%s</p></div></html>", 79 width, 80 displayValue, 81 Utils.escapeReservedCharactersHTML(shortDescription)); 80 82 } 81 83 … … 89 91 90 92 /** 91 * Returns the value to display.93 * Returns the contents of the current item view. 92 94 * @return the value to display 93 95 */ … … 102 104 */ 103 105 public String getShortDescription(boolean translated) { 104 returntranslated106 String shortDesc = translated 105 107 ? Utils.firstNonNull(locale_short_description, tr(short_description)) 106 108 : short_description; 109 return shortDesc == null ? "" : shortDesc; 107 110 } 108 111 … … 113 116 return KeyedItem.DIFFERENT; 114 117 String displayValue = getDisplayValue(); 115 return displayValue != null ? displayValue.replaceAll(" <.*>", "") : ""; // remove additional markup, e.g. <br>118 return displayValue != null ? displayValue.replaceAll("\\s*<.*>\\s*", " ") : ""; // remove additional markup, e.g. <br> 116 119 } 117 120 -
trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Roles.java
r17609 r18221 201 201 i.addToPanel(proles); 202 202 } 203 proles.applyComponentOrientation(support.getDefaultComponentOrientation()); 203 204 p.add(proles, GBC.eol()); 204 205 } -
trunk/src/org/openstreetmap/josm/gui/tagging/presets/items/Text.java
r18208 r18221 4 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 5 6 import java.util.ArrayList; 6 7 import java.awt.Color; 7 8 import java.awt.Component; … … 24 25 25 26 import org.openstreetmap.josm.data.osm.Tag; 26 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField; 27 import org.openstreetmap.josm.data.tagging.ac.AutoCompletionItem; 28 import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBoxEditor; 29 import org.openstreetmap.josm.gui.tagging.ac.AutoCompComboBoxModel; 30 import org.openstreetmap.josm.gui.tagging.ac.AutoCompTextField; 27 31 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager; 28 32 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItem; … … 31 35 import org.openstreetmap.josm.gui.widgets.JosmComboBox; 32 36 import org.openstreetmap.josm.gui.widgets.JosmTextField; 37 import org.openstreetmap.josm.gui.widgets.OrientationAction; 33 38 import org.openstreetmap.josm.tools.GBC; 34 39 import org.openstreetmap.josm.tools.Logging; … … 70 75 public boolean addToPanel(JPanel p, TaggingPresetItemGuiSupport support) { 71 76 77 AutoCompComboBoxModel<AutoCompletionItem> model = new AutoCompComboBoxModel<>(); 78 List<String> keys = new ArrayList<>(); 79 keys.add(key); 80 if (alternative_autocomplete_keys != null) { 81 for (String k : alternative_autocomplete_keys.split(",", -1)) { 82 keys.add(k); 83 } 84 } 85 getAllForKeys(keys).forEach(model::addElement); 86 87 AutoCompTextField<AutoCompletionItem> textField; 88 AutoCompComboBoxEditor<AutoCompletionItem> editor = null; 89 72 90 // find out if our key is already used in the selection. 73 91 Usage usage = determineTextUsage(support.getSelected(), key); 74 AutoCompletingTextField textField = new AutoCompletingTextField(); 75 if ( alternative_autocomplete_keys != null) {76 initAutoCompletionField(textField, (key + ',' + alternative_autocomplete_keys).split(",", -1));92 93 if (usage.unused() || usage.hasUniqueValue()) { 94 textField = new AutoCompTextField<>(); 77 95 } else { 78 initAutoCompletionField(textField, key); 96 editor = new AutoCompComboBoxEditor<>(); 97 textField = editor.getEditorComponent(); 98 } 99 textField.setModel(model); 100 value = textField; 101 102 if (length > 0) { 103 textField.setMaxTextLength(length); 79 104 } 80 105 if (TaggingPresetItem.DISPLAY_KEYS_AS_HINT.get()) { 81 106 textField.setHint(key); 82 }83 if (length > 0) {84 textField.setMaxChars((int) length);85 107 } 86 108 if (usage.unused()) { … … 111 133 value = textField; 112 134 originalValue = usage.getFirst(); 113 } else { 114 // the objects have different values 135 } 136 if (editor != null) { 137 // The selected primitives have different values for this key. <b>Note:</b> this 138 // cannot be an AutoCompComboBox because the values in the dropdown are different from 139 // those we autocomplete on. 115 140 JosmComboBox<String> comboBox = new JosmComboBox<>(usage.values.toArray(new String[0])); 116 141 comboBox.setEditable(true); 117 comboBox.setEditor( textField);142 comboBox.setEditor(editor); 118 143 comboBox.getEditor().setItem(DIFFERENT); 119 144 value = comboBox; … … 180 205 p.add(label, GBC.std().insets(0, 0, 10, 0)); 181 206 p.add(value, GBC.eol().fill(GBC.HORIZONTAL)); 207 label.applyComponentOrientation(support.getDefaultComponentOrientation()); 182 208 value.setToolTipText(getKeyTooltipText()); 209 value.applyComponentOrientation(OrientationAction.getNamelikeOrientation(key)); 183 210 return true; 184 211 } … … 253 280 } 254 281 255 private void setupListeners(AutoComp letingTextFieldtextField, TaggingPresetItemGuiSupport support) {282 private void setupListeners(AutoCompTextField<AutoCompletionItem> textField, TaggingPresetItemGuiSupport support) { 256 283 // value_templates don't work well with multiple selected items because, 257 284 // as the command queue is currently implemented, we can only save … … 266 293 Logging.trace("Evaluating value_template {0} for key {1} from {2} with new value {3} => {4}", 267 294 valueTemplate, key, source, newValue, valueTemplateText); 268 textField.set Item(valueTemplateText);295 textField.setText(valueTemplateText); 269 296 if (originalValue != null && !originalValue.equals(valueTemplateText)) { 270 297 textField.setForeground(Color.RED); -
trunk/src/org/openstreetmap/josm/gui/widgets/AbstractIdTextField.java
r18211 r18221 5 5 6 6 import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils; 7 import org.openstreetmap.josm.gui.tagging.ac.AutoCompTextField; 7 8 import org.openstreetmap.josm.tools.Logging; 8 9 import org.openstreetmap.josm.tools.Utils; … … 14 15 * @since 5765 15 16 */ 16 public abstract class AbstractIdTextField<T extends AbstractTextComponentValidator> extends JosmTextField{17 public abstract class AbstractIdTextField<T extends AbstractTextComponentValidator> extends AutoCompTextField<String> { 17 18 18 19 protected final transient T validator; -
trunk/src/org/openstreetmap/josm/gui/widgets/JosmComboBox.java
r18173 r18221 2 2 package org.openstreetmap.josm.gui.widgets; 3 3 4 4 5 import java.awt.Component; 6 import java.awt.ComponentOrientation; 5 7 import java.awt.Dimension; 6 import java.awt.event.MouseAdapter; 7 import java.awt.event.MouseEvent; 8 import java.awt.Graphics; 9 import java.awt.GraphicsConfiguration; 10 import java.awt.Insets; 11 import java.awt.Point; 12 import java.awt.Rectangle; 13 import java.awt.Toolkit; 8 14 import java.beans.PropertyChangeEvent; 9 15 import java.beans.PropertyChangeListener; 10 import java.util.Arrays;11 import java.util.Collection;12 import java.util.List;13 import java.util.stream.Collectors;14 import java.util.stream.IntStream;15 16 16 17 import javax.swing.ComboBoxEditor; 17 import javax.swing.ComboBoxModel;18 import javax.swing.DefaultComboBoxModel;19 18 import javax.swing.JComboBox; 20 19 import javax.swing.JList; 20 import javax.swing.JScrollPane; 21 21 import javax.swing.JTextField; 22 import javax.swing.plaf.basic.ComboPopup; 22 import javax.swing.ListCellRenderer; 23 import javax.swing.border.Border; 24 import javax.swing.event.PopupMenuEvent; 25 import javax.swing.event.PopupMenuListener; 23 26 import javax.swing.text.JTextComponent; 24 27 25 import org.openstreetmap.josm. gui.util.GuiHelper;28 import org.openstreetmap.josm.spi.preferences.Config; 26 29 27 30 /** 28 * Class overriding each {@link JComboBox} in JOSM to control consistently the number of displayed items at once.<br> 29 * This is needed because of the default Java behaviour that may display the top-down list off the screen (see #7917). 31 * Base class for all comboboxes in JOSM. 32 * <p> 33 * This combobox will show as many rows as possible without covering the combox itself. It makes 34 * sure the list will never go outside the screen (see #7917). You may limit the number of rows 35 * shown with the configuration: {@code gui.combobox.maximum-row-count}. 36 * <p> 37 * This combobox uses a {@link JosmTextField} for its editor component. 38 * 30 39 * @param <E> the type of the elements of this combo box 31 *32 40 * @since 5429 (creation) 33 41 * @since 7015 (generics for Java 7) 34 42 */ 35 public class JosmComboBox<E> extends JComboBox<E> { 36 37 private final ContextMenuHandler handler = new ContextMenuHandler(); 38 39 /** 40 * Creates a <code>JosmComboBox</code> with a default data model. 43 public class JosmComboBox<E> extends JComboBox<E> implements PopupMenuListener, PropertyChangeListener { 44 /** 45 * Limits the number of rows that this combobox will show. 46 */ 47 public static final String PROP_MAXIMUM_ROW_COUNT = "gui.combobox.maximum-row-count"; 48 49 /** the configured maximum row count or null */ 50 private Integer configMaximumRowCount = null; 51 52 /** 53 * The preferred height of the combobox when closed. Use if the items in the list dropdown are 54 * taller than the item in the editor, as in some comboboxes in the preset dialog. -1 to use 55 * the height of the tallest item in the list. 56 */ 57 private int preferredHeight = -1; 58 59 /** greyed text to display in the editor when the selected value is empty */ 60 private String hint; 61 62 /** 63 * Creates a {@code JosmComboBox} with a {@link JosmComboBoxModel} data model. 41 64 * The default data model is an empty list of objects. 42 65 * Use <code>addItem</code> to add items. By default the first item 43 66 * in the data model becomes selected. 44 *45 * @see DefaultComboBoxModel46 67 */ 47 68 public JosmComboBox() { 48 init(null); 49 } 50 51 /** 52 * Creates a <code>JosmComboBox</code> with a default data model and 69 super(new JosmComboBoxModel<E>()); 70 init(); 71 } 72 73 /** 74 * Creates a {@code JosmComboBox} with a {@link JosmComboBoxModel} data model and 53 75 * the specified prototype display value. 54 76 * The default data model is an empty list of objects. … … 60 82 * displaying a scroll bar 61 83 * 62 * @see DefaultComboBoxModel63 84 * @since 5450 64 */ 85 * @deprecated use {@link #setPrototypeDisplayValue} instead. 86 */ 87 @Deprecated 65 88 public JosmComboBox(E prototypeDisplayValue) { 66 init(prototypeDisplayValue); 67 } 68 69 /** 70 * Creates a <code>JosmComboBox</code> that takes its items from an 71 * existing <code>ComboBoxModel</code>. Since the 72 * <code>ComboBoxModel</code> is provided, a combo box created using 73 * this constructor does not create a default combo box model and 74 * may impact how the insert, remove and add methods behave. 75 * 76 * @param aModel the <code>ComboBoxModel</code> that provides the 77 * displayed list of items 78 * @see DefaultComboBoxModel 79 */ 80 public JosmComboBox(ComboBoxModel<E> aModel) { 89 super(new JosmComboBoxModel<E>()); 90 setPrototypeDisplayValue(prototypeDisplayValue); 91 init(); 92 } 93 94 /** 95 * Creates a {@code JosmComboBox} that takes it items from an existing {@link JosmComboBoxModel} 96 * data model. 97 * 98 * @param aModel the model that provides the displayed list of items 99 */ 100 public JosmComboBox(JosmComboBoxModel<E> aModel) { 81 101 super(aModel); 82 List<E> list = IntStream.range(0, aModel.getSize()) 83 .mapToObj(aModel::getElementAt) 84 .collect(Collectors.toList()); 85 init(findPrototypeDisplayValue(list)); 86 } 87 88 /** 89 * Creates a <code>JosmComboBox</code> that contains the elements 102 init(); 103 } 104 105 /** 106 * Creates a {@code JosmComboBox} that takes it items from an existing {@link JosmComboBoxModel} 107 * data model and sets the specified prototype display value. 108 * 109 * @param aModel the model that provides the displayed list of items 110 * @param prototypeDisplayValue use this item to size the combobox (may be null) 111 * @deprecated use {@link #setPrototypeDisplayValue} instead. 112 */ 113 @Deprecated 114 public JosmComboBox(JosmComboBoxModel<E> aModel, E prototypeDisplayValue) { 115 super(aModel); 116 setPrototypeDisplayValue(prototypeDisplayValue); 117 init(); 118 } 119 120 /** 121 * Creates a {@code JosmComboBox} that contains the elements 90 122 * in the specified array. By default the first item in the array 91 123 * (and therefore the data model) becomes selected. 92 124 * 93 125 * @param items an array of objects to insert into the combo box 94 * @see DefaultComboBoxModel95 126 */ 96 127 public JosmComboBox(E[] items) { 97 super(items); 98 init(findPrototypeDisplayValue(Arrays.asList(items))); 128 super(new JosmComboBoxModel<E>()); 129 init(); 130 for (E elem : items) { 131 getModel().addElement(elem); 132 } 133 } 134 135 private void init() { 136 configMaximumRowCount = Config.getPref().getInt(PROP_MAXIMUM_ROW_COUNT, 9999); 137 setEditor(new JosmComboBoxEditor()); 138 // listen when the popup shows up so we can maximize its height 139 addPopupMenuListener(this); 140 } 141 142 /** 143 * Returns the {@link JosmComboBoxModel} currently used. 144 * 145 * @return the model or null 146 */ 147 @Override 148 public JosmComboBoxModel<E> getModel() { 149 return (JosmComboBoxModel<E>) dataModel; 150 } 151 152 @Override 153 public void setEditor(ComboBoxEditor newEditor) { 154 if (editor != null) { 155 editor.getEditorComponent().removePropertyChangeListener(this); 156 } 157 super.setEditor(newEditor); 158 if (editor != null) { 159 // listen to orientation changes in the editor 160 editor.getEditorComponent().addPropertyChangeListener(this); 161 } 99 162 } 100 163 … … 105 168 * @since 9484 106 169 */ 107 public J TextField getEditorComponent() {108 return (J TextField) getEditor().getEditorComponent();170 public JosmTextField getEditorComponent() { 171 return (JosmTextField) (editor == null ? null : editor.getEditorComponent()); 109 172 } 110 173 … … 116 179 */ 117 180 public String getText() { 118 return getEditorComponent().getText(); 181 JosmTextField tf = getEditorComponent(); 182 return tf == null ? null : tf.getText(); 119 183 } 120 184 … … 126 190 */ 127 191 public void setText(String value) { 128 getEditorComponent().setText(value); 129 } 130 131 /** 132 * Finds the prototype display value to use among the given possible candidates. 133 * @param possibleValues The possible candidates that will be iterated. 134 * @return The value that needs the largest display height on screen. 135 * @since 5558 136 */ 137 protected final E findPrototypeDisplayValue(Collection<E> possibleValues) { 138 E result = null; 139 int maxHeight = -1; 140 if (possibleValues != null) { 141 // Remind old prototype to restore it later 142 E oldPrototype = getPrototypeDisplayValue(); 143 // Get internal JList to directly call the renderer 144 @SuppressWarnings("rawtypes") 145 JList list = getList(); 146 try { 147 // Index to give to renderer 148 int i = 0; 149 for (E value : possibleValues) { 150 if (value != null) { 151 // With a "classic" renderer, we could call setPrototypeDisplayValue(value) + getPreferredSize() 152 // but not with TaggingPreset custom renderer that return a dummy height if index is equal to -1 153 // So we explicitly call the renderer by simulating a correct index for the current value 154 @SuppressWarnings("unchecked") 155 Component c = getRenderer().getListCellRendererComponent(list, value, i, true, true); 156 if (c != null) { 157 // Get the real preferred size for the current value 158 Dimension dim = c.getPreferredSize(); 159 if (dim.height > maxHeight) { 160 // Larger ? This is our new prototype 161 maxHeight = dim.height; 162 result = value; 163 } 164 } 165 } 166 i++; 192 JosmTextField tf = getEditorComponent(); 193 if (tf != null) 194 tf.setText(value); 195 } 196 197 /** 198 * Selects an item and/or sets text 199 * 200 * Selects the item whose {@code toString()} equals {@code text}. If an item could not be found, 201 * selects nothing and sets the text anyway. 202 * 203 * @param text the text to select and set 204 * @return the item or null 205 */ 206 public E setSelectedItemText(String text) { 207 E item = getModel().find(text); 208 setSelectedItem(item); 209 if (text == null || !text.equals(getText())) 210 setText(text); 211 return item; 212 } 213 214 /* Hint handling */ 215 216 /** 217 * Returns the hint text 218 * @return the hint text 219 */ 220 public String getHint() { 221 return hint; 222 } 223 224 /** 225 * Sets the hint to display when no text has been entered. 226 * 227 * @param hint the hint to set 228 * @return the old hint 229 * @since 18221 230 */ 231 public String setHint(String hint) { 232 String old = hint; 233 this.hint = hint; 234 JosmTextField tf = getEditorComponent(); 235 if (tf != null) 236 tf.setHint(hint); 237 return old; 238 } 239 240 @Override 241 public void setComponentOrientation(ComponentOrientation o) { 242 if (o.isLeftToRight() != getComponentOrientation().isLeftToRight()) { 243 super.setComponentOrientation(o); 244 getEditorComponent().setComponentOrientation(o); 245 // the button doesn't move over without this 246 revalidate(); 247 } 248 } 249 250 /** 251 * Return true if the combobox should display the hint text. 252 * 253 * @return whether to display the hint text 254 * @since 18221 255 */ 256 public boolean displayHint() { 257 return !isEditable() && hint != null && !hint.isEmpty() && getText().isEmpty(); // && !isFocusOwner(); 258 } 259 260 /** 261 * Overrides the calculated height. See: {@link #setPreferredHeight(int)}. 262 * 263 * @since 18221 264 */ 265 @Override 266 public Dimension getPreferredSize() { 267 Dimension d = super.getPreferredSize(); 268 if (preferredHeight != -1) 269 d.height = preferredHeight; 270 return d; 271 } 272 273 /** 274 * Sets the preferred height of the combobox editor. 275 * <p> 276 * A combobox editor is automatically sized to accomodate the widest and the tallest items in 277 * the list. In the Preset dialogs we show more of an item in the list than in the editor, so 278 * the editor becomes too big. With this method we can set the editor height to a fixed value. 279 * <p> 280 * Set this to -1 to get the default behaviour back. 281 * 282 * See also: #6157 283 * 284 * @param height the preferred height or -1 285 * @return the old preferred height 286 * @see #setPreferredSize 287 * @since 18221 288 */ 289 public int setPreferredHeight(int height) { 290 int old = preferredHeight; 291 preferredHeight = height; 292 return old; 293 } 294 295 /** 296 * Get the dropdown list component 297 * 298 * @return the list or null 299 */ 300 @SuppressWarnings("unchecked") 301 public JList<E> getList() { 302 Object popup = getUI().getAccessibleChild(this, 0); 303 if (popup != null && popup instanceof javax.swing.plaf.basic.ComboPopup) { 304 return ((javax.swing.plaf.basic.ComboPopup) popup).getList(); 305 } 306 return null; 307 } 308 309 // get the popup list 310 311 /** 312 * Draw the hint text for read-only comboboxes. 313 * <p> 314 * The obvious way -- to call {@code setText(hint)} and {@code setForeground(gray)} on the 315 * {@code JLabel} returned by the list cell renderer -- unfortunately does not work out well 316 * because many UIs change the foreground color or the enabled state of the {@code JLabel} after 317 * the list cell renderer has returned ({@code BasicComboBoxUI}). Other UIs don't honor the 318 * label color at all ({@code SynthLabelUI}). 319 * <p> 320 * We use the same approach as in {@link JosmTextField}. The only problem we face is to get the 321 * coordinates of the text inside the combobox. Fortunately even read-only comboboxes have a 322 * (partially configured) editor component, although they don't use it. We configure that editor 323 * just enough to call {@link JTextField#modelToView modelToView} and 324 * {@link javax.swing.JComponent#getBaseline getBaseline} on it, thus obtaining the text 325 * coordinates. 326 * 327 * @see javax.swing.plaf.basic.BasicComboBoxUI#paintCurrentValue 328 * @see javax.swing.plaf.synth.SynthLabelUI#paint 329 */ 330 @Override 331 protected void paintComponent(Graphics g) { 332 super.paintComponent(g); 333 JosmTextField editor = getEditorComponent(); 334 if (displayHint() && editor != null) { 335 if (editor.getSize().width == 0) { 336 Dimension dimen = getSize(); 337 Insets insets = getInsets(); 338 // a fake configuration not too far from reality 339 editor.setSize(dimen.width - insets.left - insets.right, 340 dimen.height - insets.top - insets.bottom); 341 } 342 editor.drawHint(g); 343 } 344 } 345 346 /** 347 * Empties the internal undo manager, if any. 348 * <p> 349 * Used in the {@link org.openstreetmap.josm.gui.io.UploadDialog UploadDialog}. 350 * @since 14977 351 */ 352 public final void discardAllUndoableEdits() { 353 getEditorComponent().discardAllUndoableEdits(); 354 } 355 356 /** 357 * Limits the popup height. 358 * <p> 359 * Limits the popup height to the available screen space either below or above the combobox, 360 * whichever is bigger. To find the maximum number of rows that fit the screen, it does the 361 * reverse of the calculation done in 362 * {@link javax.swing.plaf.basic.BasicComboPopup#getPopupLocation}. 363 * 364 * @see javax.swing.plaf.basic.BasicComboBoxUI#getAccessibleChild 365 */ 366 @Override 367 public void popupMenuWillBecomeVisible(PopupMenuEvent ev) { 368 // Get the combobox bounds. 369 Rectangle bounds = new Rectangle(getLocationOnScreen(), getSize()); 370 371 // Get the screen bounds of the screen (of a multi-screen setup) we are on. 372 Rectangle screenBounds; 373 GraphicsConfiguration gc = getGraphicsConfiguration(); 374 Toolkit toolkit = Toolkit.getDefaultToolkit(); 375 if (gc != null) { 376 Insets screenInsets = toolkit.getScreenInsets(gc); 377 screenBounds = gc.getBounds(); 378 screenBounds.x += screenInsets.left; 379 screenBounds.y += screenInsets.top; 380 screenBounds.width -= (screenInsets.left + screenInsets.right); 381 screenBounds.height -= (screenInsets.top + screenInsets.bottom); 382 } else { 383 screenBounds = new Rectangle(new Point(), toolkit.getScreenSize()); 384 } 385 int freeAbove = bounds.y - screenBounds.y; 386 int freeBelow = (screenBounds.y + screenBounds.height) - (bounds.y + bounds.height); 387 388 try { 389 // First try an implementation-dependent method to get the exact number. 390 JList<E> jList = getList(); 391 392 // Calculate the free space available on screen 393 Insets insets = jList.getInsets(); 394 // A small fudge factor that accounts for the displacement of the popup relative to the 395 // combobox and the popup shadow. 396 int fudge = 4; 397 int free = Math.max(freeAbove, freeBelow) - (insets.top + insets.bottom) - fudge; 398 if (jList.getParent() instanceof JScrollPane) { 399 JScrollPane scroller = (JScrollPane) jList.getParent(); 400 Border border = scroller.getViewportBorder(); 401 if (border != null) { 402 insets = border.getBorderInsets(null); 403 free -= insets.top + insets.bottom; 167 404 } 168 } finally { 169 // Restore original prototype 170 setPrototypeDisplayValue(oldPrototype); 171 } 172 } 173 return result; 174 } 175 176 @SuppressWarnings("unchecked") 177 protected final JList<Object> getList() { 178 return IntStream.range(0, getUI().getAccessibleChildrenCount(this)) 179 .mapToObj(i -> getUI().getAccessibleChild(this, i)) 180 .filter(child -> child instanceof ComboPopup) 181 .findFirst() 182 .map(child -> ((ComboPopup) child).getList()) 183 .orElse(null); 184 } 185 186 /** 187 * Set the prototypeCellValue property and calculate the height of the dropdown. 188 */ 189 @Override 190 public void setPrototypeDisplayValue(E prototype) { 191 if (prototype != null) { 192 super.setPrototypeDisplayValue(prototype); 193 int screenHeight = GuiHelper.getScreenSize().height; 194 // Compute maximum number of visible items based on the preferred size of the combo box. 195 // This assumes that items have the same height as the combo box, which is not granted by the look and feel 196 int maxsize = (screenHeight/getPreferredSize().height) / 2; 197 // If possible, adjust the maximum number of items with the real height of items 198 // It is not granted this works on every platform (tested OK on Windows) 199 JList<Object> list = getList(); 200 if (list != null) { 201 if (!prototype.equals(list.getPrototypeCellValue())) { 202 list.setPrototypeCellValue(prototype); 203 } 204 int height = list.getFixedCellHeight(); 205 if (height > 0) { 206 maxsize = (screenHeight/height) / 2; 405 border = scroller.getBorder(); 406 if (border != null) { 407 insets = border.getBorderInsets(null); 408 free -= insets.top + insets.bottom; 207 409 } 208 410 } 209 setMaximumRowCount(Math.max(getMaximumRowCount(), maxsize)); 210 } 211 } 212 213 protected final void init(E prototype) { 214 init(prototype, true); 215 } 216 217 protected final void init(E prototype, boolean registerPropertyChangeListener) { 218 setPrototypeDisplayValue(prototype); 219 // Handle text contextual menus for editable comboboxes 220 if (registerPropertyChangeListener) { 221 addPropertyChangeListener("editable", handler); 222 addPropertyChangeListener("editor", handler); 223 } 224 } 225 226 protected class ContextMenuHandler extends MouseAdapter implements PropertyChangeListener { 227 228 private JTextComponent component; 229 private PopupMenuLauncher launcher; 230 231 @Override 232 public void propertyChange(PropertyChangeEvent evt) { 233 if ("editable".equals(evt.getPropertyName())) { 234 if (evt.getNewValue().equals(Boolean.TRUE)) { 235 enableMenu(); 236 } else { 237 disableMenu(); 238 } 239 } else if ("editor".equals(evt.getPropertyName())) { 240 disableMenu(); 241 if (isEditable()) { 242 enableMenu(); 243 } 411 412 // Calculate how many rows fit into the free space. Rows may have variable heights. 413 int rowCount = Math.min(configMaximumRowCount, getItemCount()); 414 ListCellRenderer<? super E> r = jList.getCellRenderer(); // must take this from list, not combo: flatlaf bug 415 int i, h = 0; 416 for (i = 0; i < rowCount; ++i) { 417 Component c = r.getListCellRendererComponent(jList, getModel().getElementAt(i), i, false, false); 418 h += c.getPreferredSize().height; 419 if (h >= free) 420 break; 244 421 } 245 } 246 247 private void enableMenu() { 248 if (launcher == null && editor != null) { 249 Component editorComponent = editor.getEditorComponent(); 250 if (editorComponent instanceof JTextComponent) { 251 component = (JTextComponent) editorComponent; 252 component.addMouseListener(this); 253 launcher = TextContextualPopupMenu.enableMenuFor(component, true); 254 } 255 } 256 } 257 258 private void disableMenu() { 259 if (launcher != null) { 260 TextContextualPopupMenu.disableMenuFor(component, launcher); 261 launcher = null; 262 component.removeMouseListener(this); 263 component = null; 264 } 265 } 266 267 private void discardAllUndoableEdits() { 268 if (launcher != null) { 269 launcher.discardAllUndoableEdits(); 270 } 271 } 272 273 @Override 274 public void mousePressed(MouseEvent e) { 275 processEvent(e); 276 } 277 278 @Override 279 public void mouseReleased(MouseEvent e) { 280 processEvent(e); 281 } 282 283 private void processEvent(MouseEvent e) { 284 if (launcher != null && !e.isPopupTrigger() && launcher.getMenu().isShowing()) { 285 launcher.getMenu().setVisible(false); 286 } 287 } 288 } 289 290 /** 291 * Reinitializes this {@link JosmComboBox} to the specified values. This may be needed if a custom renderer is used. 292 * @param values The values displayed in the combo box. 293 * @since 5558 294 */ 295 public final void reinitialize(Collection<E> values) { 296 init(findPrototypeDisplayValue(values), false); 297 discardAllUndoableEdits(); 298 } 299 300 /** 301 * Empties the internal undo manager, if any. 302 * @since 14977 303 */ 304 public final void discardAllUndoableEdits() { 305 handler.discardAllUndoableEdits(); 422 setMaximumRowCount(i); 423 // Logging.debug("free = {0}, h = {1}, i = {2}, bounds = {3}, screenBounds = {4}", free, h, i, bounds, screenBounds); 424 } catch (Exception ex) { 425 setMaximumRowCount(8); // the default 426 } 427 } 428 429 @Override 430 public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { 431 // Who cares? 432 } 433 434 @Override 435 public void popupMenuCanceled(PopupMenuEvent e) { 436 // Who cares? 437 } 438 439 @Override 440 public void propertyChange(PropertyChangeEvent evt) { 441 // follow our editor's orientation 442 if ("componentOrientation".equals(evt.getPropertyName())) { 443 setComponentOrientation((ComponentOrientation) evt.getNewValue()); 444 } 306 445 } 307 446 } -
trunk/src/org/openstreetmap/josm/gui/widgets/JosmTextField.java
r18211 r18221 3 3 4 4 import java.awt.Color; 5 import java.awt.ComponentOrientation; 6 import java.awt.Font; 5 7 import java.awt.FontMetrics; 6 8 import java.awt.Graphics; 7 9 import java.awt.Graphics2D; 8 10 import java.awt.Insets; 11 import java.awt.Point; 9 12 import java.awt.RenderingHints; 13 import java.awt.event.ComponentEvent; 14 import java.awt.event.ComponentListener; 10 15 import java.awt.event.FocusEvent; 11 16 import java.awt.event.FocusListener; 12 13 import javax.swing.BorderFactory; 17 import java.beans.PropertyChangeEvent; 18 import java.beans.PropertyChangeListener; 19 14 20 import javax.swing.Icon; 15 21 import javax.swing.JTextField; 16 import javax.swing.border.Border; 22 import javax.swing.RepaintManager; 23 import javax.swing.JMenuItem; 24 import javax.swing.JPopupMenu; 25 import javax.swing.UIManager; 26 import javax.swing.text.BadLocationException; 17 27 import javax.swing.text.Document; 18 28 … … 31 41 * @since 5886 32 42 */ 33 public class JosmTextField extends JTextField implements Destroyable, FocusListener {43 public class JosmTextField extends JTextField implements Destroyable, ComponentListener, FocusListener, PropertyChangeListener { 34 44 35 45 private final PopupMenuLauncher launcher; 36 46 private String hint; 37 47 private Icon icon; 38 private int leftInsets; 48 private Point iconPos; 49 private Insets originalMargin; 50 private OrientationAction orientationAction; 39 51 40 52 /** … … 78 90 super(doc, text, columns); 79 91 launcher = TextContextualPopupMenu.enableMenuFor(this, undoRedo); 92 93 // There seems to be a bug in Swing 8 that components with Bidi enabled are smaller than 94 // without. (eg. 23px vs 21px in height, maybe a font thing). Usually Bidi starts disabled 95 // but gets enabled whenever RTL text is loaded. To avoid trashing the layout we enable 96 // Bidi by default. See also {@link #drawHint()}. 97 getDocument().putProperty("i18n", Boolean.TRUE); 98 99 // the menu and hotkey to change text orientation 100 orientationAction = new OrientationAction(this); 101 orientationAction.addPropertyChangeListener(this); 102 JPopupMenu menu = launcher.getMenu(); 103 menu.addSeparator(); 104 menu.add(new JMenuItem(orientationAction)); 105 getInputMap().put(OrientationAction.getShortcutKey(), orientationAction); 106 80 107 // Fix minimum size when columns are specified 81 108 if (columns > 0) { … … 83 110 } 84 111 addFocusListener(this); 112 addComponentListener(this); 85 113 // Workaround for Java bug 6322854 86 114 JosmPasswordField.workaroundJdkBug6322854(this); 115 originalMargin = getMargin(); 87 116 } 88 117 … … 148 177 * Sets the hint to display when no text has been entered. 149 178 * @param hint the hint to set 150 * @since 7505 151 */ 152 public final void setHint(String hint) { 179 * @return the old hint 180 * @since 18221 (signature) 181 */ 182 public String setHint(String hint) { 183 String old = hint; 153 184 this.hint = hint; 185 return old; 186 } 187 188 /** 189 * Return true if the textfield should display the hint text. 190 * 191 * @return whether to display the hint text 192 * @since 18221 193 */ 194 public boolean displayHint() { 195 return !Utils.isEmpty(hint) && getText().isEmpty() && !isFocusOwner(); 154 196 } 155 197 … … 170 212 public void setIcon(Icon icon) { 171 213 this.icon = icon; 214 if (icon == null) { 215 setMargin(originalMargin); 216 } 217 positionIcon(); 218 } 219 220 private void positionIcon() { 172 221 if (icon != null) { 173 this.leftInsets = getInsets().left; 174 Border original = getBorder(); 175 Border margin = BorderFactory.createEmptyBorder(0, icon.getIconWidth(), 0, 0); 176 setBorder(original == null ? margin : BorderFactory.createCompoundBorder(original, margin)); 222 Insets margin = (Insets) originalMargin.clone(); 223 int hGap = (getHeight() - icon.getIconHeight()) / 2; 224 if (getComponentOrientation() == ComponentOrientation.RIGHT_TO_LEFT) { 225 margin.right += icon.getIconWidth() + 2 * hGap; 226 iconPos = new Point(getWidth() - icon.getIconWidth() - hGap, hGap); 227 } else { 228 margin.left += icon.getIconWidth() + 2 * hGap; 229 iconPos = new Point(hGap, hGap); 230 } 231 setMargin(margin); 232 } 233 } 234 235 @Override 236 public void setComponentOrientation(ComponentOrientation o) { 237 if (o.isLeftToRight() != getComponentOrientation().isLeftToRight()) { 238 super.setComponentOrientation(o); 239 positionIcon(); 177 240 } 178 241 } … … 186 249 } 187 250 188 @Override 189 public void paint(Graphics g) { 190 super.paint(g); 251 /** 252 * Returns the color for hint texts. 253 * @return the Color for hint texts 254 */ 255 public static Color getHintTextColor() { 256 Color color = UIManager.getColor("TextField[Disabled].textForeground"); // Nimbus? 257 if (color == null) 258 color = UIManager.getColor("TextField.inactiveForeground"); 259 if (color == null) 260 color = Color.GRAY; 261 return color; 262 } 263 264 /** 265 * Returns the font for hint texts. 266 * @return the font for hint texts 267 */ 268 public static Font getHintFont() { 269 return UIManager.getFont("TextField.font"); 270 } 271 272 @Override 273 public void paintComponent(Graphics g) { 274 super.paintComponent(g); 191 275 if (icon != null) { 192 int h = getHeight() - icon.getIconHeight(); 193 icon.paintIcon(this, g, Math.min(leftInsets, h / 2), h / 2); 194 } 195 if (!Utils.isEmpty(hint) && getText().isEmpty() && !isFocusOwner()) { 196 // Taken from http://stackoverflow.com/a/24571681/2257172 197 int h = getHeight(); 198 if (g instanceof Graphics2D) { 199 ((Graphics2D) g).setRenderingHint( 200 RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); 201 } 202 Insets ins = getInsets(); 203 FontMetrics fm = g.getFontMetrics(); 204 int c0 = getBackground().getRGB(); 205 int c1 = getForeground().getRGB(); 206 int m = 0xfefefefe; 207 int c2 = ((c0 & m) >>> 1) + ((c1 & m) >>> 1); 208 g.setColor(new Color(c2, true)); 209 g.drawString(hint, ins.left, h / 2 + fm.getAscent() / 2 - 2); 210 } 276 icon.paintIcon(this, g, iconPos.x, iconPos.y); 277 } 278 if (displayHint()) { 279 // Logging.debug("drawing textfield hint: {0}", getHint()); 280 drawHint(g); 281 } 282 } 283 284 /** 285 * Draws the hint text over the editor component. 286 * 287 * @param g the graphics context 288 */ 289 public void drawHint(Graphics g) { 290 int x; 291 try { 292 x = modelToView(0).x; 293 } catch (BadLocationException exc) { 294 return; // can't happen 295 } 296 // Taken from http://stackoverflow.com/a/24571681/2257172 297 if (g instanceof Graphics2D) { 298 ((Graphics2D) g).setRenderingHint( 299 RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); 300 } 301 g.setColor(getHintTextColor()); 302 g.setFont(getHintFont()); 303 if (getComponentOrientation().isLeftToRight()) { 304 g.drawString(getHint(), x, getBaseline(getWidth(), getHeight())); 305 } else { 306 FontMetrics metrics = g.getFontMetrics(g.getFont()); 307 int dx = metrics.stringWidth(getHint()); 308 g.drawString(getHint(), x - dx, getBaseline(getWidth(), getHeight())); 309 } 310 // Needed to avoid endless repaint loop if we accidentally draw over the insets. This may 311 // easily happen because a change in text orientation invalidates the textfield and 312 // following that the preferred size gets smaller. (Bug in Swing?) 313 RepaintManager.currentManager(this).markCompletelyClean(this); 211 314 } 212 315 … … 217 320 map.keyDetector.setEnabled(false); 218 321 } 219 repaint(); 322 if (e != null && e.getOppositeComponent() != null) { 323 // Select all characters when the change of focus occurs inside JOSM only. 324 // When switching from another application, it is annoying, see #13747 325 selectAll(); 326 } 327 positionIcon(); 328 repaint(); // get rid of hint 220 329 } 221 330 … … 226 335 map.keyDetector.setEnabled(true); 227 336 } 228 repaint(); 337 repaint(); // paint hint 229 338 } 230 339 … … 234 343 TextContextualPopupMenu.disableMenuFor(this, launcher); 235 344 } 345 346 @Override 347 public void componentResized(ComponentEvent e) { 348 positionIcon(); 349 } 350 351 @Override 352 public void componentMoved(ComponentEvent e) { 353 } 354 355 @Override 356 public void componentShown(ComponentEvent e) { 357 } 358 359 @Override 360 public void componentHidden(ComponentEvent e) { 361 } 362 363 @Override 364 public void propertyChange(PropertyChangeEvent evt) { 365 // command from the menu / shortcut key 366 if ("orientationAction".equals(evt.getPropertyName())) { 367 setComponentOrientation((ComponentOrientation) evt.getNewValue()); 368 } 369 } 236 370 }
Note:
See TracChangeset
for help on using the changeset viewer.