Ticket #25030: multiline_editing_support.patch
| File multiline_editing_support.patch, 40.3 KB (added by , 25 minutes ago) |
|---|
-
src/org/openstreetmap/josm/gui/dialogs/properties/PropertiesCellRenderer.java
diff --git src/org/openstreetmap/josm/gui/dialogs/properties/PropertiesCellRenderer.java src/org/openstreetmap/josm/gui/dialogs/properties/PropertiesCellRenderer.java index 58f9de53bc..06953cef48 100644
import java.util.concurrent.CopyOnWriteArrayList; 16 16 17 17 import javax.swing.JLabel; 18 18 import javax.swing.JTable; 19 import javax.swing.JTextArea; 20 import javax.swing.SwingConstants; 19 21 import javax.swing.UIManager; 20 22 import javax.swing.table.DefaultTableCellRenderer; 21 23 import javax.swing.table.TableCellRenderer; … … public class PropertiesCellRenderer extends DefaultTableCellRenderer { 132 134 } 133 135 ((JLabel) c).putClientProperty("html.disable", enableHTML ? null : Boolean.TRUE); // Fix #8730 134 136 ((JLabel) c).setText(str); 137 138 // Needed for multiline values and doesn't hurt single line ones 139 if (column == 0) { 140 ((JLabel) c).setVerticalAlignment(SwingConstants.TOP); 141 } else if (!enableHTML) { 142 JTextArea jta = new JTextArea(str); 143 jta.setForeground(c.getForeground()); 144 jta.setBackground(c.getBackground()); 145 146 c = jta; 147 } 148 135 149 if (DISCARDABLE.get()) { 136 150 String key = null; 137 151 if (column == 0) { -
src/org/openstreetmap/josm/gui/dialogs/properties/PropertiesDialog.java
diff --git src/org/openstreetmap/josm/gui/dialogs/properties/PropertiesDialog.java src/org/openstreetmap/josm/gui/dialogs/properties/PropertiesDialog.java index de272a5f72..ec4afca39c 100644
implements DataSelectionListener, ActiveLayerChangeListener, PropertyChangeListe 696 696 } 697 697 698 698 private void autoresizeTagTable() { 699 // resize table's rows and columns to fit content 699 700 if (Boolean.TRUE.equals(PROP_AUTORESIZE_TAGS_TABLE.get())) { 700 // resize table's columns to fit content701 701 TableHelper.computeColumnsWidth(tagTable); 702 702 } 703 TableHelper.computeRowsHeight(tagTable); 703 704 } 704 705 705 706 private void updateUi(IPrimitive primitive) { -
src/org/openstreetmap/josm/gui/dialogs/properties/TagEditHelper.java
diff --git src/org/openstreetmap/josm/gui/dialogs/properties/TagEditHelper.java src/org/openstreetmap/josm/gui/dialogs/properties/TagEditHelper.java index 8124c7b60a..b6194c4c30 100644
import static org.openstreetmap.josm.tools.I18n.tr; 6 6 import static org.openstreetmap.josm.tools.I18n.trn; 7 7 8 8 import java.awt.BorderLayout; 9 import java.awt.CardLayout; 10 import java.awt.Color; 9 11 import java.awt.Component; 10 12 import java.awt.ComponentOrientation; 11 13 import java.awt.Container; … … import java.util.stream.IntStream; 42 44 43 45 import javax.swing.AbstractAction; 44 46 import javax.swing.Action; 47 import javax.swing.BorderFactory; 45 48 import javax.swing.Box; 46 49 import javax.swing.ButtonGroup; 47 50 import javax.swing.ImageIcon; … … import javax.swing.JOptionPane; 54 57 import javax.swing.JPanel; 55 58 import javax.swing.JPopupMenu; 56 59 import javax.swing.JRadioButtonMenuItem; 60 import javax.swing.JScrollPane; 57 61 import javax.swing.JTable; 58 62 import javax.swing.KeyStroke; 59 63 import javax.swing.ListCellRenderer; … … import org.openstreetmap.josm.gui.tagging.presets.items.KeyedItem; 96 100 import org.openstreetmap.josm.gui.util.GuiHelper; 97 101 import org.openstreetmap.josm.gui.util.WindowGeometry; 98 102 import org.openstreetmap.josm.gui.widgets.JosmListCellRenderer; 103 import org.openstreetmap.josm.gui.widgets.JosmTextArea; 99 104 import org.openstreetmap.josm.gui.widgets.OrientationAction; 100 105 import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher; 101 106 import org.openstreetmap.josm.io.XmlWriter; … … import org.openstreetmap.josm.tools.Utils; 112 117 * @since 5633 113 118 */ 114 119 public class TagEditHelper { 120 /** 121 * Default for {@link #MULTILINE_TAGS}. 122 * Values taken from fields marked as textareas in https://github.com/openstreetmap/id-tagging-schema 123 */ 124 private static final List<String> DEFAULT_MULTILINE_TAGS = Arrays.asList("defibrillator:location", "description", "fixme", 125 "note", "comment", "inscription", "label"); 126 127 /** 128 * Keys of tags for which line breaks are valid characters 129 */ 130 public static final ListProperty MULTILINE_TAGS = new ListProperty("properties.multiline-tags", DEFAULT_MULTILINE_TAGS); 115 131 116 132 private final JTable tagTable; 117 133 private final DefaultTableModel tagData; … … public class TagEditHelper { 461 477 * @return the edited item with whitespaces removed 462 478 * @since 18173 463 479 */ 464 public static String getEditItem(AutoCompComboBox<AutoCompletionItem> cb) { 465 return Utils.removeWhiteSpaces(cb.getEditorItemAsString()); 480 public static String getEditItem(JComponent cb) { 481 if (cb instanceof JosmTextArea) { 482 return Utils.removeWhiteSpaces(Utils.MULTILINE_WHITESPACE_PATTERN, ((JosmTextArea) cb).getText()); 483 } else { 484 return Utils.removeWhiteSpaces(((AutoCompComboBox<AutoCompletionItem>) cb).getEditorItemAsString()); 485 } 466 486 } 467 487 468 488 /** … … public class TagEditHelper { 580 600 581 601 final String selection = m.size() != 1 ? KeyedItem.DIFFERENT_I18N : m.entrySet().iterator().next().getKey(); 582 602 583 values = new AutoCompComboBox<>(); 584 values.getModel().setComparator(Comparator.naturalOrder()); 585 values.setRenderer(new TEHListCellRenderer(values, values.getRenderer(), valueCount.get(key))); 586 values.setEditable(true); 587 values.setPrototypeDisplayValue(new AutoCompletionItem(DUMMY)); 588 values.getModel().addAllElements(valueList); 589 values.setSelectedItemText(selection); 603 textCards = new CardLayout(); 604 textPanel = new JPanel(textCards); 605 606 // create a combobox for single line tags 607 AutoCompComboBox<AutoCompletionItem> vals = new AutoCompComboBox<>(); 608 vals.getModel().setComparator(Comparator.naturalOrder()); 609 vals.setRenderer(new TEHListCellRenderer(vals, vals.getRenderer(), valueCount.get(key))); 610 vals.setEditable(true); 611 vals.setPrototypeDisplayValue(new AutoCompletionItem(DUMMY)); 612 vals.getModel().addAllElements(valueList); 613 vals.setSelectedItemText(selection); 614 values = vals; 615 616 // create a sub-panel to properly size the single line combobox 617 JPanel singleLinePanel = new JPanel(new GridBagLayout()); 618 singleLinePanel.setBorder(BorderFactory.createLineBorder(Color.gray)); 619 singleLinePanel.add(values, GBC.eol().fill(GridBagConstraints.HORIZONTAL).anchor(GridBagConstraints.NORTH).weight(1, 1)); 620 621 // create a text area for multiline tags 622 values_multiline = new JosmTextArea(); 623 values_multiline.setText(selection); 624 // Scroll multiline text area back to the top 625 values_multiline.setCaretPosition(0); 626 values_multiline.setRows(5); 627 values_multiline.setFont(vals.getFont()); 628 629 // add both cards 630 textPanel.add(singleLinePanel, SINGLELINE); 631 textPanel.add(new JScrollPane(values_multiline), MULTILINE); 590 632 591 633 p.add(Box.createVerticalStrut(5), GBC.eol()); 592 p.add(new JLabel(tr("Value")), GBC.std() );634 p.add(new JLabel(tr("Value")), GBC.std().anchor(GridBagConstraints.NORTH)); 593 635 p.add(Box.createHorizontalStrut(10), GBC.std()); 594 p.add( values, GBC.eol().fill(GridBagConstraints.HORIZONTAL));636 p.add(textPanel, GBC.eol().fill(GridBagConstraints.HORIZONTAL)); 595 637 p.add(Box.createVerticalStrut(2), GBC.eol()); 596 638 597 639 p.applyComponentOrientation(OrientationAction.getDefaultComponentOrientation()); 598 640 keys.applyComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT); 599 641 values.applyComponentOrientation(OrientationAction.getNamelikeOrientation(keys.getText())); 642 values_multiline.applyComponentOrientation(values.getComponentOrientation()); 643 644 // immediately show the multiline card if the key is multiline 645 if (TagEditHelper.MULTILINE_TAGS.get().contains(key)) { 646 multiline = true; 647 textCards.show(textPanel, MULTILINE); 648 } 600 649 601 650 setContent(mainPanel, false); 602 651 … … public class TagEditHelper { 615 664 616 665 @Override 617 666 public void performTagEdit() { 618 String value = getEditItem( values);667 String value = getEditItem(multiline ? values_multiline : values); 619 668 value = Normalizer.normalize(value, Normalizer.Form.NFC); 620 669 if (value.isEmpty()) { 621 670 value = null; // delete the key … … public class TagEditHelper { 628 677 } 629 678 if (key.equals(newkey) && KeyedItem.DIFFERENT_I18N.equals(value)) 630 679 return; 631 if (value != null && key.equals(getEditItem(keys)) && m.size() == 1 && m.containsKey(getEditItem(values))) 680 if (value != null && key.equals(getEditItem(keys)) && m.size() == 1 681 && m.containsKey(getEditItem(multiline ? values_multiline : values))) 632 682 return; // see #22814: avoid to create a command that wouldn't change anything 633 683 if (key.equals(newkey) || value == null) { 634 684 UndoRedoHandler.getInstance().add(new ChangePropertyCommand(sel, newkey, value)); … … public class TagEditHelper { 669 719 } 670 720 671 721 protected abstract class AbstractTagsDialog extends ExtendedDialog implements AutoCompListener, FocusListener, PopupMenuListener { 722 // name to select which card to show for the value field 723 protected static final String SINGLELINE = "singleline"; 724 protected static final String MULTILINE = "multiline"; 725 672 726 protected AutoCompComboBox<AutoCompletionItem> keys; 727 728 // maintain two separate components for single line and multiline values 673 729 protected AutoCompComboBox<AutoCompletionItem> values; 730 protected JosmTextArea values_multiline; 731 732 // select which of the two to show with a card layout 733 protected CardLayout textCards; 734 protected JPanel textPanel; 735 protected boolean multiline; 736 674 737 protected boolean initialFocusOnKey = true; 675 738 /** 676 739 * The 'values' model is currently holding values for this key. Used for lazy-loading of values. … … public class TagEditHelper { 800 863 public void focusLost(FocusEvent e) { 801 864 // update the values combobox orientation if the key changed 802 865 values.applyComponentOrientation(OrientationAction.getNamelikeOrientation(keys.getText())); 866 values_multiline.applyComponentOrientation(values.getComponentOrientation()); 803 867 // update the auto-completion setting for the given tag key 804 868 values.setAutocompleteEnabled(autocompleteValuesForKey(keys.getText())); 869 870 boolean previousMultiline = multiline; 871 if (TagEditHelper.MULTILINE_TAGS.get().contains(keys.getText())) { 872 multiline = true; 873 } else { 874 multiline = false; 875 876 } 877 878 if (previousMultiline != multiline) { 879 // update which values field is shown and sync text between them 880 if (multiline) { 881 values_multiline.setText(values.getText()); 882 textCards.show(textPanel, MULTILINE); 883 } else { 884 values.setText(values_multiline.getText()); 885 textCards.show(textPanel, SINGLELINE); 886 } 887 888 // user focused one values field after changing to/from a multiline key. 889 // redirect the focus request to the text field that's now shown. 890 if (e.getOppositeComponent() == values.getEditorComponent()) { 891 SwingUtilities.invokeLater(() -> values_multiline.requestFocusInWindow()); 892 } else if (e.getOppositeComponent() == values_multiline) { 893 SwingUtilities.invokeLater(() -> values.getEditorComponent().requestFocusInWindow()); 894 } 895 } 805 896 } 806 897 807 898 protected void updateOkButtonIcon() { … … public class TagEditHelper { 881 972 mainPanel.add(keys, GBC.eop().fill(GridBagConstraints.HORIZONTAL)); 882 973 mainPanel.add(new JLabel(tr("Choose a value")), GBC.eol()); 883 974 975 textCards = new CardLayout(); 976 textPanel = new JPanel(textCards); 977 978 // create a combobox for single line tags 884 979 values = new AutoCompComboBox<>(); 885 980 values.setPrototypeDisplayValue(new AutoCompletionItem(DUMMY)); 886 981 values.setEditable(true); 887 982 values.getModel().setComparator(Comparator.naturalOrder()); 888 983 values.setAutocompleteEnabled(AUTOCOMPLETE_VALUES.get()); 889 984 890 mainPanel.add(values, GBC.eop().fill(GridBagConstraints.HORIZONTAL)); 985 // create a sub-panel to properly size the single line combobox 986 JPanel singleLinePanel = new JPanel(new GridBagLayout()); 987 singleLinePanel.setBorder(BorderFactory.createLineBorder(Color.gray)); 988 singleLinePanel.add(values, GBC.eol().fill(GridBagConstraints.HORIZONTAL).anchor(GridBagConstraints.NORTH).weight(1, 1)); 989 990 // create a text area for multiline tags 991 values_multiline = new JosmTextArea(); 992 values_multiline.setRows(5); 993 values_multiline.setFont(values.getFont()); 994 995 // add both cards 996 textPanel.add(singleLinePanel, SINGLELINE); 997 textPanel.add(new JScrollPane(values_multiline), MULTILINE); 998 999 mainPanel.add(textPanel, GBC.eop().fill(GridBagConstraints.HORIZONTAL)); 891 1000 892 1001 cacheRecentTags(); 893 1002 autocomplete = AutoCompletionManager.of(OsmDataManager.getInstance().getActiveDataSet()); … … public class TagEditHelper { 907 1016 .ifPresent(tag -> { 908 1017 keys.setSelectedItemText(tag.getKey()); 909 1018 values.setSelectedItemText(tag.getValue()); 1019 values_multiline.setText(tag.getValue()); 1020 // Scroll multiline text area back to the top 1021 values_multiline.setCaretPosition(0); 910 1022 }); 911 1023 1024 // immediately show the multiline field if the key is multiline 1025 if (TagEditHelper.MULTILINE_TAGS.get().contains(keys.getText())) { 1026 multiline = true; 1027 textCards.show(textPanel, MULTILINE); 1028 } 1029 912 1030 913 1031 keys.addActionListener(ignore -> updateOkButtonIcon()); 914 1032 values.addActionListener(ignore -> updateOkButtonIcon()); … … public class TagEditHelper { 1096 1214 // fix #7951, #8298 - update list of values before setting value (?) 1097 1215 updateValueModel(autocomplete, DEFAULT_AC_ITEM_COMPARATOR); 1098 1216 values.setSelectedItemText(t.getValue()); 1099 values.requestFocus(); 1217 values_multiline.setText(t.getValue()); 1218 1219 if (TagEditHelper.MULTILINE_TAGS.get().contains(t.getKey())) { 1220 multiline = true; 1221 textCards.show(textPanel, MULTILINE); 1222 SwingUtilities.invokeLater(() -> values_multiline.requestFocusInWindow()); 1223 } else { 1224 multiline = false; 1225 textCards.show(textPanel, SINGLELINE); 1226 SwingUtilities.invokeLater(() -> values.getEditorComponent().requestFocusInWindow()); 1227 } 1100 1228 } 1101 1229 }; 1102 1230 /* POSSIBLE SHORTCUTS: 1,2,3,4,5,6,7,8,9,0=10 */ … … public class TagEditHelper { 1284 1412 */ 1285 1413 private void performTagAdding(Collection<OsmPrimitive> selection) { 1286 1414 String key = getEditItem(keys); 1287 String value = getEditItem( values);1415 String value = getEditItem(multiline ? values_multiline : values); 1288 1416 if (key.isEmpty() || value.isEmpty()) 1289 1417 return; 1290 1418 for (Tagged osm : selection) { … … public class TagEditHelper { 1310 1438 protected void clearEntries() { 1311 1439 keys.getEditor().setItem(""); 1312 1440 values.getEditor().setItem(""); 1441 values_multiline.setText(""); 1313 1442 } 1314 1443 1315 1444 /** -
src/org/openstreetmap/josm/gui/history/HistoryBrowser.java
diff --git src/org/openstreetmap/josm/gui/history/HistoryBrowser.java src/org/openstreetmap/josm/gui/history/HistoryBrowser.java index 3c747f7fe1..8be3849e20 100644
public class HistoryBrowser extends JPanel implements Destroyable, ChangeListene 176 176 @Override 177 177 public void stateChanged(ChangeEvent e) { 178 178 tagInfoViewer.adjustWidths(); 179 tagInfoViewer.adjustHeights(); 179 180 } 180 181 } -
src/org/openstreetmap/josm/gui/history/TagInfoViewer.java
diff --git src/org/openstreetmap/josm/gui/history/TagInfoViewer.java src/org/openstreetmap/josm/gui/history/TagInfoViewer.java index 354bafcb52..4d90a3d0e4 100644
public class TagInfoViewer extends HistoryViewerPanel { 123 123 adjustWidths(current, maxWidth); 124 124 } 125 125 126 /** 127 * Use current data to adjust preferredHeight for both tables. 128 */ 129 public void adjustHeights() { 130 // Update row height so the content fits for both tables individually 131 TableHelper.computeRowsHeight(reference); 132 TableHelper.computeRowsHeight(current); 133 134 // Now set height to the max of the two so the rows align again 135 for (int row = 0; row < reference.getRowCount(); row++) { 136 int referenceHeight = reference.getRowHeight(row); 137 int currentHeight = current.getRowHeight(row); 138 139 int maxHeight = Math.max(referenceHeight, currentHeight); 140 reference.setRowHeight(row, maxHeight); 141 current.setRowHeight(row, maxHeight); 142 } 143 } 144 126 145 private static void adjustWidths(JTable table, int maxWidth) { 127 146 for (int column = 0; column < table.getColumnCount(); column++) { 128 147 TableHelper.adjustColumnWidth(table, column, maxWidth); -
src/org/openstreetmap/josm/gui/history/TagTableCellRenderer.java
diff --git src/org/openstreetmap/josm/gui/history/TagTableCellRenderer.java src/org/openstreetmap/josm/gui/history/TagTableCellRenderer.java index 6502e22337..d202e9f974 100644
import static org.openstreetmap.josm.tools.I18n.tr; 5 5 6 6 import java.awt.Color; 7 7 import java.awt.Component; 8 import java.awt.ComponentOrientation; 8 9 9 10 import javax.swing.BorderFactory; 10 import javax.swing.JLabel;11 11 import javax.swing.JTable; 12 import javax.swing. SwingConstants;12 import javax.swing.JTextArea; 13 13 import javax.swing.table.TableCellRenderer; 14 14 15 15 import org.openstreetmap.josm.data.osm.history.HistoryOsmPrimitive; … … import org.openstreetmap.josm.gui.util.GuiHelper; 19 19 * The {@link TableCellRenderer} for a list of tags in {@link HistoryBrowser} 20 20 * 21 21 */ 22 public class TagTableCellRenderer extends J Labelimplements TableCellRenderer {22 public class TagTableCellRenderer extends JTextArea implements TableCellRenderer { 23 23 /** 24 24 * The background color for a selected row that has the focus. 25 25 */ … … public class TagTableCellRenderer extends JLabel implements TableCellRenderer { 73 73 74 74 setText(text); 75 75 setToolTipText(tooltip != null ? tooltip : text); 76 setHorizontalAlignment(column == TagTableColumnModel.COLUMN_VERSION ? SwingConstants.TRAILING : SwingConstants.LEADING); 76 // Abuse component orientation since JTextAreas don't have setHorizontalAlignment 77 ComponentOrientation orientation; 78 if (column == TagTableColumnModel.COLUMN_VERSION) { 79 orientation = ComponentOrientation.RIGHT_TO_LEFT; 80 } else { 81 orientation = ComponentOrientation.UNKNOWN; 82 } 83 setComponentOrientation(orientation); 77 84 TwoColumnDiff.Item.DiffItemType diffItemType = model.getDiffItemType(key, column == TagTableColumnModel.COLUMN_VALUE); 78 85 GuiHelper.setBackgroundReadable(this, diffItemType.getColor(isSelected, table.hasFocus())); 79 86 return this; -
src/org/openstreetmap/josm/gui/tagging/presets/items/Text.java
diff --git src/org/openstreetmap/josm/gui/tagging/presets/items/Text.java src/org/openstreetmap/josm/gui/tagging/presets/items/Text.java index 32f83243c4..dbf0296729 100644
import static org.openstreetmap.josm.tools.I18n.tr; 5 5 6 6 import java.awt.Color; 7 7 import java.awt.Component; 8 import java.awt.Container; 8 9 import java.awt.GridBagLayout; 9 10 import java.awt.Insets; 10 11 import java.util.ArrayList; 11 12 import java.util.Collection; 12 13 import java.util.Collections; 13 14 import java.util.List; 14 import java.util.regex.Pattern;15 15 16 16 import javax.swing.AbstractButton; 17 17 import javax.swing.BorderFactory; … … import javax.swing.JButton; 20 20 import javax.swing.JComponent; 21 21 import javax.swing.JLabel; 22 22 import javax.swing.JPanel; 23 import javax.swing.JScrollPane; 23 24 import javax.swing.JToggleButton; 25 import javax.swing.JViewport; 26 import javax.swing.text.JTextComponent; 24 27 25 28 import org.openstreetmap.josm.data.osm.Tag; 26 29 import org.openstreetmap.josm.data.tagging.ac.AutoCompletionItem; … … import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItem; 32 35 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport; 33 36 import org.openstreetmap.josm.gui.util.DocumentAdapter; 34 37 import org.openstreetmap.josm.gui.widgets.JosmComboBox; 38 import org.openstreetmap.josm.gui.widgets.JosmTextArea; 35 39 import org.openstreetmap.josm.gui.widgets.JosmTextField; 36 40 import org.openstreetmap.josm.gui.widgets.OrientationAction; 37 41 import org.openstreetmap.josm.tools.GBC; … … import org.xml.sax.SAXException; 46 50 * Text field type. 47 51 */ 48 52 public class Text extends KeyedItem { 49 private static final Pattern MULTILINE_WHITESPACE_PATTERN = Pattern.compile("[\\s&&[^\n]]+");50 51 53 private static int auto_increment_selected; // NOSONAR 52 54 53 55 /** The default value for the item. If not specified, the current value of the key is chosen as default (if applicable). Defaults to "". */ … … public class Text extends KeyedItem { 82 84 } 83 85 getAllForKeys(keys).forEach(model::addElement); 84 86 85 AutoCompTextField<AutoCompletionItem>textField;87 JTextComponent textField; 86 88 AutoCompComboBoxEditor<AutoCompletionItem> editor = null; 87 89 88 90 // find out if our key is already used in the selection. 89 91 Usage usage = determineTextUsage(support.getSelected(), key); 90 92 91 93 if (usage.unused() || usage.hasUniqueValue()) { 92 textField = new AutoCompTextField<>(); 94 if (this.multiline) { 95 JosmTextArea area = new JosmTextArea(); 96 area.setBorder((new JosmTextField()).getBorder()); 97 area.setRows(5); 98 99 textField = area; 100 } else { 101 textField = new AutoCompTextField<AutoCompletionItem>(); 102 } 93 103 } else { 94 104 editor = new AutoCompComboBoxEditor<>(); 95 105 textField = editor.getEditorComponent(); 96 106 } 97 textField.setModel(model);98 value = textField;99 107 100 if (length > 0) { 101 textField.setMaxTextLength(length); 108 if (this.multiline) { 109 value = new JScrollPane(textField); 110 } else { 111 value = textField; 112 } 113 114 if (textField instanceof AutoCompTextField<?>) { 115 AutoCompTextField<AutoCompletionItem> tf = (AutoCompTextField<AutoCompletionItem>) textField; 116 tf.setModel(model); 117 if (length > 0) { 118 tf.setMaxTextLength(length); 119 } 102 120 } 121 103 122 if (TaggingPresetItem.DISPLAY_KEYS_AS_HINT.get()) { 104 textField.setHint(key); 123 if (textField instanceof JosmTextField) { 124 ((JosmTextField) textField).setHint(key); 125 } else { 126 ((JosmTextArea) textField).setHint(key); 127 } 105 128 } 106 129 if (usage.unused()) { 107 130 if (auto_increment_selected != 0 && auto_increment != null) { … … public class Text extends KeyedItem { 123 146 // selected osm primitives are tagged and filling default values feature is disabled 124 147 textField.setText(""); 125 148 } 126 value = textField;127 149 originalValue = null; 128 150 } else if (usage.hasUniqueValue()) { 129 151 // all objects use the same value 130 152 textField.setText(usage.getFirst()); 131 value = textField; 153 // Scroll multiline text areas back to the top 154 textField.setCaretPosition(0); 155 132 156 originalValue = usage.getFirst(); 133 157 } 134 158 if (editor != null) { … … public class Text extends KeyedItem { 222 246 private static String getValue(Component comp) { 223 247 if (comp instanceof JosmComboBox) { 224 248 return ((JosmComboBox<?>) comp).getEditorItemAsString(); 225 } else if (comp instanceof J osmTextField) {226 return ((J osmTextField) comp).getText();227 } else if (comp instanceof JPanel ) {228 return getValue((( JPanel) comp).getComponent(0));249 } else if (comp instanceof JTextComponent) { 250 return ((JTextComponent) comp).getText(); 251 } else if (comp instanceof JPanel || comp instanceof JScrollPane || comp instanceof JViewport) { 252 return getValue(((Container) comp).getComponent(0)); 229 253 } else { 230 254 return null; 231 255 } … … public class Text extends KeyedItem { 243 267 244 268 if (this.normalize) { 245 269 if (this.multiline) { 246 v = Utils.removeWhiteSpaces( MULTILINE_WHITESPACE_PATTERN, v);270 v = Utils.removeWhiteSpaces(Utils.MULTILINE_WHITESPACE_PATTERN, v); 247 271 } else { 248 272 v = Utils.removeWhiteSpaces(v); 249 273 } … … public class Text extends KeyedItem { 285 309 } 286 310 } 287 311 288 private void setupListeners( AutoCompTextField<AutoCompletionItem>textField, TaggingPresetItemGuiSupport support) {312 private void setupListeners(JTextComponent textField, TaggingPresetItemGuiSupport support) { 289 313 // value_templates don't work well with multiple selected items because, 290 314 // as the command queue is currently implemented, we can only save 291 315 // the same value to all selected primitives, which is probably not -
src/org/openstreetmap/josm/gui/util/TableHelper.java
diff --git src/org/openstreetmap/josm/gui/util/TableHelper.java src/org/openstreetmap/josm/gui/util/TableHelper.java index 51ede3f1d0..04a6d71523 100644
public final class TableHelper { 77 77 return maxwidth; 78 78 } 79 79 80 static int getMaxHeight(JTable tbl, int row) { 81 int maxheight = 0; 82 for (int col = 0; col < tbl.getColumnCount(); col++) { 83 TableCellRenderer tcr = tbl.getCellRenderer(row, col); 84 Object val = tbl.getValueAt(row, col); 85 Component comp = tcr.getTableCellRendererComponent(tbl, val, false, false, row, col); 86 maxheight = Math.max(comp.getPreferredSize().height, maxheight); 87 } 88 return maxheight; 89 } 90 80 91 /** 81 92 * adjust the preferred width of column col to the maximum preferred width of the cells (including header) 82 93 * @param tbl table … … public final class TableHelper { 106 117 tbl.getColumnModel().getColumn(col).setPreferredWidth(Math.min(maxwidth+10, maxColumnWidth)); 107 118 } 108 119 120 /** 121 * adjust the preferred height of the given row to the maximum preferred height of the cells 122 * requires JTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); 123 * @param tbl table 124 * @param row row index 125 */ 126 public static void adjustRowHeight(JTable tbl, int row) { 127 int maxheight = getMaxHeight(tbl, row); 128 tbl.setRowHeight(row, maxheight); 129 } 130 109 131 /** 110 132 * adjust the table's columns to fit their content best 111 133 * requires JTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); … … public final class TableHelper { 118 140 } 119 141 } 120 142 143 /** 144 * adjust the table's rows to fit their content best 145 * requires JTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); 146 * @param tbl table 147 */ 148 public static void computeRowsHeight(JTable tbl) { 149 for (int row = 0; row < tbl.getRowCount(); row++) { 150 adjustRowHeight(tbl, row); 151 } 152 } 153 121 154 /** 122 155 * Returns a stream of all of the selected indices in the selection model, in increasing order. 123 156 * -
src/org/openstreetmap/josm/gui/widgets/JosmTextArea.java
diff --git src/org/openstreetmap/josm/gui/widgets/JosmTextArea.java src/org/openstreetmap/josm/gui/widgets/JosmTextArea.java index a01ef09e40..f5dddca9ca 100644
1 1 // License: GPL. For details, see LICENSE file. 2 2 package org.openstreetmap.josm.gui.widgets; 3 3 4 import java.awt.Color; 5 import java.awt.ComponentOrientation; 6 import java.awt.Font; 7 import java.awt.FontMetrics; 8 import java.awt.Graphics; 9 import java.awt.Graphics2D; 4 10 import java.awt.KeyboardFocusManager; 11 import java.awt.RenderingHints; 5 12 import java.awt.event.FocusEvent; 6 13 import java.awt.event.FocusListener; 14 import java.beans.PropertyChangeEvent; 15 import java.beans.PropertyChangeListener; 7 16 17 import javax.swing.JMenuItem; 18 import javax.swing.JPopupMenu; 8 19 import javax.swing.JTextArea; 20 import javax.swing.RepaintManager; 21 import javax.swing.UIManager; 22 import javax.swing.text.BadLocationException; 9 23 import javax.swing.text.Document; 10 24 11 25 import org.openstreetmap.josm.gui.MainApplication; 12 26 import org.openstreetmap.josm.gui.MapFrame; 13 27 import org.openstreetmap.josm.tools.Destroyable; 28 import org.openstreetmap.josm.tools.Logging; 29 import org.openstreetmap.josm.tools.Utils; 14 30 15 31 /** 16 32 * Subclass of {@link JTextArea} that adds a "native" context menu (cut/copy/paste/select all). 17 33 * @since 5886 18 34 */ 19 public class JosmTextArea extends JTextArea implements Destroyable, FocusListener {35 public class JosmTextArea extends JTextArea implements Destroyable, FocusListener, PropertyChangeListener { 20 36 21 37 private final PopupMenuLauncher launcher; 38 private OrientationAction orientationAction; 39 private String hint; 22 40 23 41 /** 24 42 * Constructs a new {@code JosmTextArea}. A default model is set, the initial string … … public class JosmTextArea extends JTextArea implements Destroyable, FocusListene 91 109 public JosmTextArea(Document doc, String text, int rows, int columns) { 92 110 super(doc, text, rows, columns); 93 111 launcher = TextContextualPopupMenu.enableMenuFor(this, true); 112 113 // the menu and hotkey to change text orientation 114 orientationAction = new OrientationAction(this); 115 orientationAction.addPropertyChangeListener(this); 116 JPopupMenu menu = launcher.getMenu(); 117 menu.addSeparator(); 118 menu.add(new JMenuItem(orientationAction)); 119 getInputMap().put(OrientationAction.getShortcutKey(), orientationAction); 120 94 121 addFocusListener(this); 95 122 } 96 123 124 /** 125 * Replies the hint displayed when no text has been entered. 126 * @return the hint 127 */ 128 public final String getHint() { 129 return hint; 130 } 131 132 /** 133 * Sets the hint to display when no text has been entered. 134 * @param hint the hint to set 135 * @return the old hint 136 */ 137 public String setHint(String hint) { 138 String old = this.hint; 139 this.hint = hint; 140 return old; 141 } 142 143 /** 144 * Return true if the text area should display the hint text. 145 * 146 * @return whether to display the hint text 147 */ 148 public boolean displayHint() { 149 return !Utils.isEmpty(hint) && getText().isEmpty() && !isFocusOwner(); 150 } 151 152 /** 153 * Returns the color for hint texts. 154 * @return the Color for hint texts 155 */ 156 public static Color getHintTextColor() { 157 Color color = UIManager.getColor("TextField[Disabled].textForeground"); // Nimbus? 158 if (color == null) 159 color = UIManager.getColor("TextField.inactiveForeground"); 160 if (color == null) 161 color = Color.GRAY; 162 return color; 163 } 164 165 /** 166 * Returns the font for hint texts. 167 * @return the font for hint texts 168 */ 169 public static Font getHintFont() { 170 return UIManager.getFont("TextField.font"); 171 } 172 173 @Override 174 public void paintComponent(Graphics g) { 175 super.paintComponent(g); 176 177 if (displayHint()) { 178 drawHint(g); 179 } 180 } 181 182 /** 183 * Draws the hint text over the editor component. 184 * 185 * @param g the graphics context 186 */ 187 public void drawHint(Graphics g) { 188 int x; 189 try { 190 x = (int) Math.round(modelToView2D(0).getX()); 191 } catch (BadLocationException exc) { 192 Logging.trace(exc); 193 return; // can't happen 194 } 195 // Taken from http://stackoverflow.com/a/24571681/2257172 196 if (g instanceof Graphics2D) { 197 ((Graphics2D) g).setRenderingHint( 198 RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); 199 } 200 g.setColor(getHintTextColor()); 201 g.setFont(getHintFont()); 202 if (getComponentOrientation().isLeftToRight()) { 203 g.drawString(getHint(), x, getBaseline(getWidth(), getHeight())); 204 } else { 205 FontMetrics metrics = g.getFontMetrics(g.getFont()); 206 int dx = metrics.stringWidth(getHint()); 207 g.drawString(getHint(), x - dx, getBaseline(getWidth(), getHeight())); 208 } 209 // Needed to avoid endless repaint loop if we accidentally draw over the insets. This may 210 // easily happen because a change in text orientation invalidates the text area and 211 // following that the preferred size gets smaller. (Bug in Swing?) 212 RepaintManager.currentManager(this).markCompletelyClean(this); 213 } 214 97 215 /** 98 216 * Restore default behaviour of focus transfer with TAB, overridden by {@link JTextArea}. 99 217 * @return {@code this} … … public class JosmTextArea extends JTextArea implements Destroyable, FocusListene 112 230 if (map != null) { 113 231 map.keyDetector.setEnabled(false); 114 232 } 233 234 repaint(); // get rid of hint 115 235 } 116 236 117 237 @Override … … public class JosmTextArea extends JTextArea implements Destroyable, FocusListene 120 240 if (map != null) { 121 241 map.keyDetector.setEnabled(true); 122 242 } 243 244 repaint(); // paint hint 123 245 } 124 246 125 247 @Override … … public class JosmTextArea extends JTextArea implements Destroyable, FocusListene 127 249 removeFocusListener(this); 128 250 TextContextualPopupMenu.disableMenuFor(this, launcher); 129 251 } 252 253 @Override 254 public void propertyChange(PropertyChangeEvent evt) { 255 // command from the menu / shortcut key 256 if ("orientationAction".equals(evt.getPropertyName())) { 257 setComponentOrientation((ComponentOrientation) evt.getNewValue()); 258 } 259 } 130 260 } -
src/org/openstreetmap/josm/tools/Utils.java
diff --git src/org/openstreetmap/josm/tools/Utils.java src/org/openstreetmap/josm/tools/Utils.java index fd55277504..b4c29766d1 100644
public final class Utils { 71 71 72 72 /** Pattern matching white spaces */ 73 73 public static final Pattern WHITE_SPACES_PATTERN = Pattern.compile("\\s+", Pattern.UNICODE_CHARACTER_CLASS); 74 public static final Pattern MULTILINE_WHITESPACE_PATTERN = Pattern.compile("[\\s&&[^\n]]+"); 74 75 75 76 private static final long MILLIS_OF_SECOND = TimeUnit.SECONDS.toMillis(1); 76 77 private static final long MILLIS_OF_MINUTE = TimeUnit.MINUTES.toMillis(1); -
test/unit/org/openstreetmap/josm/gui/dialogs/properties/PropertiesCellRendererTest.java
diff --git test/unit/org/openstreetmap/josm/gui/dialogs/properties/PropertiesCellRendererTest.java test/unit/org/openstreetmap/josm/gui/dialogs/properties/PropertiesCellRendererTest.java index 45902efd40..b3bdbdfb9a 100644
package org.openstreetmap.josm.gui.dialogs.properties; 3 3 4 4 import static org.junit.jupiter.api.Assertions.assertEquals; 5 5 6 import java.awt.Component; 6 7 import java.util.function.IntFunction; 7 8 8 9 import javax.swing.JLabel; 10 import javax.swing.JTextArea; 9 11 import javax.swing.JTable; 10 12 import javax.swing.table.DefaultTableModel; 11 13 … … class PropertiesCellRendererTest { 32 34 new Object[]{"elevation", "314159"}, 33 35 }, new Object[]{"key", "value"}); 34 36 JTable table = new JTable(tableModel); 35 IntFunction<String> getLabel = row -> ((JLabel) renderer.getTableCellRendererComponent( 36 table, table.getValueAt(row, 1), false, false, row, 1)).getText(); 37 IntFunction<String> getLabel = row -> { 38 Component comp = renderer.getTableCellRendererComponent( 39 table, table.getValueAt(row, 1), false, false, row, 1); 40 if (comp instanceof JLabel) { 41 return ((JLabel) comp).getText(); 42 } else { 43 return ((JTextArea) comp).getText(); 44 } 45 }; 37 46 assertEquals("<html><body><span color='#FF0000'>\u25A0</span> red</body></html>", getLabel.apply(0)); 38 47 assertEquals("<html><body><span color='#008000'>\u25A0</span> green</body></html>", getLabel.apply(1)); 39 48 assertEquals("<html><body><span color='#123'>\u25A0</span> #123</body></html>", getLabel.apply(2)); -
test/unit/org/openstreetmap/josm/gui/tagging/presets/items/TextTest.java
diff --git test/unit/org/openstreetmap/josm/gui/tagging/presets/items/TextTest.java test/unit/org/openstreetmap/josm/gui/tagging/presets/items/TextTest.java index e03e925e57..8f2d3a5417 100644
import java.util.Map; 14 14 15 15 import javax.swing.JComponent; 16 16 import javax.swing.JPanel; 17 import javax.swing.JScrollPane; 18 import javax.swing.JViewport; 19 import javax.swing.text.JTextComponent; 17 20 18 21 import org.junit.jupiter.api.Test; 19 22 import org.junit.jupiter.params.ParameterizedTest; … … import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemGuiSupport; 24 27 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetItemTest; 25 28 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetType; 26 29 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets; 30 import org.openstreetmap.josm.gui.widgets.JosmTextArea; 27 31 import org.openstreetmap.josm.gui.widgets.JosmTextField; 28 32 import org.openstreetmap.josm.testutils.annotations.Main; 29 33 … … class TextTest implements TaggingPresetItemTest { 66 70 final JPanel panel = new JPanel(); 67 71 text.addToPanel(panel, TaggingPresetItemGuiSupport.create(false)); 68 72 JComponent value = assertInstanceOf(JComponent.class, panel.getComponent(1)); 69 while (value instanceof JPanel ) {73 while (value instanceof JPanel || value instanceof JScrollPane || value instanceof JViewport) { 70 74 value = (JComponent) value.getComponent(0); 71 75 } 72 final JosmTextField textField = assertInstanceOf(JosmTextField.class, value, 73 "Until we support multiline editing, this should be a text field"); 76 final JTextComponent textField; 77 if (value instanceof JosmTextField) { 78 textField = (JosmTextField) value; 79 } else { 80 textField = assertInstanceOf(JosmTextArea.class, value, 81 "This should be either a JosmTextField or a JosmTextArea"); 82 } 74 83 textField.setText(toTest); 75 84 text.addCommands(changeCommands); 76 85 assertTrue(text.multiline);
