001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.tagging; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005import static org.openstreetmap.josm.tools.I18n.trc; 006 007import java.awt.Component; 008import java.awt.Dimension; 009import java.awt.Font; 010import java.awt.GridBagLayout; 011import java.awt.GridLayout; 012import java.awt.event.ActionEvent; 013import java.awt.event.ActionListener; 014import java.awt.event.MouseAdapter; 015import java.awt.event.MouseEvent; 016import java.io.File; 017import java.lang.reflect.Method; 018import java.lang.reflect.Modifier; 019import java.text.NumberFormat; 020import java.text.ParseException; 021import java.util.ArrayList; 022import java.util.Arrays; 023import java.util.Collection; 024import java.util.Collections; 025import java.util.EnumSet; 026import java.util.HashMap; 027import java.util.LinkedHashMap; 028import java.util.LinkedList; 029import java.util.List; 030import java.util.Map; 031import java.util.TreeSet; 032 033import javax.swing.ButtonGroup; 034import javax.swing.ImageIcon; 035import javax.swing.JButton; 036import javax.swing.JComponent; 037import javax.swing.JLabel; 038import javax.swing.JList; 039import javax.swing.JPanel; 040import javax.swing.JScrollPane; 041import javax.swing.JSeparator; 042import javax.swing.JToggleButton; 043import javax.swing.ListCellRenderer; 044import javax.swing.ListModel; 045 046import org.openstreetmap.josm.Main; 047import org.openstreetmap.josm.actions.search.SearchCompiler; 048import org.openstreetmap.josm.data.osm.OsmPrimitive; 049import org.openstreetmap.josm.data.osm.OsmUtils; 050import org.openstreetmap.josm.data.osm.Tag; 051import org.openstreetmap.josm.data.preferences.BooleanProperty; 052import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField; 053import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionItemPriority; 054import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionList; 055import org.openstreetmap.josm.gui.widgets.JosmComboBox; 056import org.openstreetmap.josm.gui.widgets.JosmTextField; 057import org.openstreetmap.josm.gui.widgets.QuadStateCheckBox; 058import org.openstreetmap.josm.gui.widgets.UrlLabel; 059import org.openstreetmap.josm.tools.GBC; 060import org.openstreetmap.josm.tools.ImageProvider; 061import org.openstreetmap.josm.tools.Predicate; 062import org.openstreetmap.josm.tools.Utils; 063import org.xml.sax.SAXException; 064 065/** 066 * Class that contains all subtypes of TaggingPresetItem, static supplementary data, types and methods 067 * @since 6068 068 */ 069public final class TaggingPresetItems { 070 private TaggingPresetItems() { 071 } 072 073 private static int auto_increment_selected = 0; 074 public static final String DIFFERENT = tr("<different>"); 075 076 private static final BooleanProperty PROP_FILL_DEFAULT = new BooleanProperty("taggingpreset.fill-default-for-tagged-primitives", false); 077 078 // cache the parsing of types using a LRU cache (http://java-planet.blogspot.com/2005/08/how-to-set-up-simple-lru-cache-using.html) 079 private static final Map<String,EnumSet<TaggingPresetType>> TYPE_CACHE = new LinkedHashMap<>(16, 1.1f, true); 080 081 /** 082 * Last value of each key used in presets, used for prefilling corresponding fields 083 */ 084 private static final Map<String,String> LAST_VALUES = new HashMap<>(); 085 086 public static class PresetListEntry { 087 public String value; 088 public String value_context; 089 public String display_value; 090 public String short_description; 091 public String icon; 092 public String icon_size; 093 public String locale_display_value; 094 public String locale_short_description; 095 private final File zipIcons = TaggingPresetReader.getZipIcons(); 096 097 // Cached size (currently only for Combo) to speed up preset dialog initialization 098 private int prefferedWidth = -1; 099 private int prefferedHeight = -1; 100 101 public String getListDisplay() { 102 if (value.equals(DIFFERENT)) 103 return "<b>"+DIFFERENT.replaceAll("<", "<").replaceAll(">", ">")+"</b>"; 104 105 if (value.isEmpty()) 106 return " "; 107 108 final StringBuilder res = new StringBuilder("<b>"); 109 res.append(getDisplayValue(true)); 110 res.append("</b>"); 111 if (getShortDescription(true) != null) { 112 // wrap in table to restrict the text width 113 res.append("<div style=\"width:300px; padding:0 0 5px 5px\">"); 114 res.append(getShortDescription(true)); 115 res.append("</div>"); 116 } 117 return res.toString(); 118 } 119 120 public ImageIcon getIcon() { 121 return icon == null ? null : loadImageIcon(icon, zipIcons, parseInteger(icon_size)); 122 } 123 124 private Integer parseInteger(String str) { 125 if (str == null || str.isEmpty()) 126 return null; 127 try { 128 return Integer.parseInt(str); 129 } catch (Exception e) { 130 // 131 } 132 return null; 133 } 134 135 public PresetListEntry() { 136 } 137 138 public PresetListEntry(String value) { 139 this.value = value; 140 } 141 142 public String getDisplayValue(boolean translated) { 143 return translated 144 ? Utils.firstNonNull(locale_display_value, tr(display_value), trc(value_context, value)) 145 : Utils.firstNonNull(display_value, value); 146 } 147 148 public String getShortDescription(boolean translated) { 149 return translated 150 ? Utils.firstNonNull(locale_short_description, tr(short_description)) 151 : short_description; 152 } 153 154 // toString is mainly used to initialize the Editor 155 @Override 156 public String toString() { 157 if (value.equals(DIFFERENT)) 158 return DIFFERENT; 159 return getDisplayValue(true).replaceAll("<.*>", ""); // remove additional markup, e.g. <br> 160 } 161 } 162 163 public static class Role { 164 public EnumSet<TaggingPresetType> types; 165 public String key; 166 public String text; 167 public String text_context; 168 public String locale_text; 169 public SearchCompiler.Match memberExpression; 170 171 public boolean required = false; 172 private long count = 0; 173 174 public void setType(String types) throws SAXException { 175 this.types = getType(types); 176 } 177 178 public void setRequisite(String str) throws SAXException { 179 if("required".equals(str)) { 180 required = true; 181 } else if(!"optional".equals(str)) 182 throw new SAXException(tr("Unknown requisite: {0}", str)); 183 } 184 185 public void setMember_expression(String member_expression) throws SAXException { 186 try { 187 this.memberExpression = SearchCompiler.compile(member_expression, true, true); 188 } catch (SearchCompiler.ParseError ex) { 189 throw new SAXException(tr("Illegal member expression: {0}", ex.getMessage()), ex); 190 } 191 } 192 193 public void setCount(String count) { 194 this.count = Long.parseLong(count); 195 } 196 197 /** 198 * Return either argument, the highest possible value or the lowest allowed value 199 */ 200 public long getValidCount(long c) { 201 if (count > 0 && !required) 202 return c != 0 ? count : 0; 203 else if (count > 0) 204 return count; 205 else if (!required) 206 return c != 0 ? c : 0; 207 else 208 return c != 0 ? c : 1; 209 } 210 211 public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel) { 212 String cstring; 213 if(count > 0 && !required) { 214 cstring = "0,"+count; 215 } else if(count > 0) { 216 cstring = String.valueOf(count); 217 } else if(!required) { 218 cstring = "0-..."; 219 } else { 220 cstring = "1-..."; 221 } 222 if(locale_text == null) { 223 if (text != null) { 224 if(text_context != null) { 225 locale_text = trc(text_context, fixPresetString(text)); 226 } else { 227 locale_text = tr(fixPresetString(text)); 228 } 229 } 230 } 231 p.add(new JLabel(locale_text+":"), GBC.std().insets(0,0,10,0)); 232 p.add(new JLabel(key), GBC.std().insets(0,0,10,0)); 233 p.add(new JLabel(cstring), types == null ? GBC.eol() : GBC.std().insets(0,0,10,0)); 234 if(types != null){ 235 JPanel pp = new JPanel(); 236 for(TaggingPresetType t : types) { 237 pp.add(new JLabel(ImageProvider.get(t.getIconName()))); 238 } 239 p.add(pp, GBC.eol()); 240 } 241 return true; 242 } 243 } 244 245 /** 246 * Enum denoting how a match (see {@link TaggingPresetItem#matches}) is performed. 247 */ 248 public static enum MatchType { 249 250 /** 251 * Neutral, i.e., do not consider this item for matching. 252 */ 253 NONE("none"), 254 /** 255 * Positive if key matches, neutral otherwise. 256 */ 257 KEY("key"), 258 /** 259 * Positive if key matches, negative otherwise. 260 */ 261 KEY_REQUIRED("key!"), 262 /** 263 * Positive if key and value matches, negative otherwise. 264 */ 265 KEY_VALUE("keyvalue"); 266 267 private final String value; 268 269 private MatchType(String value) { 270 this.value = value; 271 } 272 273 public String getValue() { 274 return value; 275 } 276 277 public static MatchType ofString(String type) { 278 for (MatchType i : EnumSet.allOf(MatchType.class)) { 279 if (i.getValue().equals(type)) 280 return i; 281 } 282 throw new IllegalArgumentException(type + " is not allowed"); 283 } 284 } 285 286 public static class Usage { 287 TreeSet<String> values; 288 boolean hadKeys = false; 289 boolean hadEmpty = false; 290 public boolean hasUniqueValue() { 291 return values.size() == 1 && !hadEmpty; 292 } 293 294 public boolean unused() { 295 return values.isEmpty(); 296 } 297 public String getFirst() { 298 return values.first(); 299 } 300 301 public boolean hadKeys() { 302 return hadKeys; 303 } 304 } 305 306 /** 307 * A tagging preset item displaying a localizable text. 308 * @since 6190 309 */ 310 public abstract static class TaggingPresetTextItem extends TaggingPresetItem { 311 312 /** 313 * The text to display 314 */ 315 public String text; 316 317 /** 318 * The context used for translating {@link #text} 319 */ 320 public String text_context; 321 322 /** 323 * The localized version of {@link #text} 324 */ 325 public String locale_text; 326 327 protected final void initializeLocaleText(String defaultText) { 328 if (locale_text == null) { 329 if (text == null) { 330 locale_text = defaultText; 331 } else if (text_context != null) { 332 locale_text = trc(text_context, fixPresetString(text)); 333 } else { 334 locale_text = tr(fixPresetString(text)); 335 } 336 } 337 } 338 339 @Override 340 void addCommands(List<Tag> changedTags) { 341 } 342 343 protected String fieldsToString() { 344 return (text != null ? "text=" + text + ", " : "") 345 + (text_context != null ? "text_context=" + text_context + ", " : "") 346 + (locale_text != null ? "locale_text=" + locale_text : ""); 347 } 348 349 @Override 350 public String toString() { 351 return getClass().getSimpleName() + " [" + fieldsToString() + "]"; 352 } 353 } 354 355 public static class Label extends TaggingPresetTextItem { 356 357 @Override 358 public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel, boolean presetInitiallyMatches) { 359 initializeLocaleText(null); 360 addLabel(p, locale_text); 361 return false; 362 } 363 364 public static void addLabel(JPanel p, String label) { 365 p.add(new JLabel(label), GBC.eol()); 366 } 367 } 368 369 public static class Link extends TaggingPresetTextItem { 370 371 /** 372 * The link to display 373 */ 374 public String href; 375 376 /** 377 * The localized version of {@link #href} 378 */ 379 public String locale_href; 380 381 @Override 382 public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel, boolean presetInitiallyMatches) { 383 initializeLocaleText(tr("More information about this feature")); 384 String url = locale_href; 385 if (url == null) { 386 url = href; 387 } 388 if (url != null) { 389 p.add(new UrlLabel(url, locale_text, 2), GBC.eol().insets(0, 10, 0, 0)); 390 } 391 return false; 392 } 393 394 @Override 395 protected String fieldsToString() { 396 return super.fieldsToString() 397 + (href != null ? "href=" + href + ", " : "") 398 + (locale_href != null ? "locale_href=" + locale_href + ", " : ""); 399 } 400 } 401 402 public static class PresetLink extends TaggingPresetItem { 403 404 public String preset_name = ""; 405 406 @Override 407 boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel, boolean presetInitiallyMatches) { 408 final String presetName = preset_name; 409 final TaggingPreset t = Utils.filter(TaggingPresets.getTaggingPresets(), new Predicate<TaggingPreset>() { 410 @Override 411 public boolean evaluate(TaggingPreset object) { 412 return presetName.equals(object.name); 413 } 414 }).iterator().next(); 415 if (t == null) return false; 416 JLabel lbl = new PresetLabel(t); 417 lbl.addMouseListener(new MouseAdapter() { 418 @Override 419 public void mouseClicked(MouseEvent arg0) { 420 t.actionPerformed(null); 421 } 422 }); 423 p.add(lbl, GBC.eol().fill(GBC.HORIZONTAL)); 424 return false; 425 } 426 427 @Override 428 void addCommands(List<Tag> changedTags) { 429 } 430 } 431 432 public static class Roles extends TaggingPresetItem { 433 434 public final List<Role> roles = new LinkedList<>(); 435 436 @Override 437 public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel, boolean presetInitiallyMatches) { 438 p.add(new JLabel(" "), GBC.eol()); // space 439 if (!roles.isEmpty()) { 440 JPanel proles = new JPanel(new GridBagLayout()); 441 proles.add(new JLabel(tr("Available roles")), GBC.std().insets(0, 0, 10, 0)); 442 proles.add(new JLabel(tr("role")), GBC.std().insets(0, 0, 10, 0)); 443 proles.add(new JLabel(tr("count")), GBC.std().insets(0, 0, 10, 0)); 444 proles.add(new JLabel(tr("elements")), GBC.eol()); 445 for (Role i : roles) { 446 i.addToPanel(proles, sel); 447 } 448 p.add(proles, GBC.eol()); 449 } 450 return false; 451 } 452 453 @Override 454 public void addCommands(List<Tag> changedTags) { 455 } 456 } 457 458 public static class Optional extends TaggingPresetTextItem { 459 460 // TODO: Draw a box around optional stuff 461 @Override 462 public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel, boolean presetInitiallyMatches) { 463 initializeLocaleText(tr("Optional Attributes:")); 464 p.add(new JLabel(" "), GBC.eol()); // space 465 p.add(new JLabel(locale_text), GBC.eol()); 466 p.add(new JLabel(" "), GBC.eol()); // space 467 return false; 468 } 469 } 470 471 public static class Space extends TaggingPresetItem { 472 473 @Override 474 public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel, boolean presetInitiallyMatches) { 475 p.add(new JLabel(" "), GBC.eol()); // space 476 return false; 477 } 478 479 @Override 480 public void addCommands(List<Tag> changedTags) { 481 } 482 483 @Override 484 public String toString() { 485 return "Space"; 486 } 487 } 488 489 /** 490 * Class used to represent a {@link JSeparator} inside tagging preset window. 491 * @since 6198 492 */ 493 public static class ItemSeparator extends TaggingPresetItem { 494 495 @Override 496 public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel, boolean presetInitiallyMatches) { 497 p.add(new JSeparator(), GBC.eol().fill(GBC.HORIZONTAL).insets(0, 5, 0, 5)); 498 return false; 499 } 500 501 @Override 502 public void addCommands(List<Tag> changedTags) { 503 } 504 505 @Override 506 public String toString() { 507 return "ItemSeparator"; 508 } 509 } 510 511 public abstract static class KeyedItem extends TaggingPresetItem { 512 513 public String key; 514 public String text; 515 public String text_context; 516 public String match = getDefaultMatch().getValue(); 517 518 public abstract MatchType getDefaultMatch(); 519 public abstract Collection<String> getValues(); 520 521 @Override 522 Boolean matches(Map<String, String> tags) { 523 switch (MatchType.ofString(match)) { 524 case NONE: 525 return null; 526 case KEY: 527 return tags.containsKey(key) ? true : null; 528 case KEY_REQUIRED: 529 return tags.containsKey(key); 530 case KEY_VALUE: 531 return tags.containsKey(key) && (getValues().contains(tags.get(key))); 532 default: 533 throw new IllegalStateException(); 534 } 535 } 536 537 @Override 538 public String toString() { 539 return "KeyedItem [key=" + key + ", text=" + text 540 + ", text_context=" + text_context + ", match=" + match 541 + "]"; 542 } 543 } 544 545 public static class Key extends KeyedItem { 546 547 public String value; 548 549 @Override 550 public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel, boolean presetInitiallyMatches) { 551 return false; 552 } 553 554 @Override 555 public void addCommands(List<Tag> changedTags) { 556 changedTags.add(new Tag(key, value)); 557 } 558 559 @Override 560 public MatchType getDefaultMatch() { 561 return MatchType.KEY_VALUE; 562 } 563 564 @Override 565 public Collection<String> getValues() { 566 return Collections.singleton(value); 567 } 568 569 @Override 570 public String toString() { 571 return "Key [key=" + key + ", value=" + value + ", text=" + text 572 + ", text_context=" + text_context + ", match=" + match 573 + "]"; 574 } 575 } 576 577 public static class Text extends KeyedItem { 578 579 public String locale_text; 580 public String default_; 581 public String originalValue; 582 public String use_last_as_default = "false"; 583 public String auto_increment; 584 public String length; 585 public String alternative_autocomplete_keys; 586 587 private JComponent value; 588 589 @Override public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel, boolean presetInitiallyMatches) { 590 591 // find out if our key is already used in the selection. 592 Usage usage = determineTextUsage(sel, key); 593 AutoCompletingTextField textField = new AutoCompletingTextField(); 594 if (alternative_autocomplete_keys != null) { 595 initAutoCompletionField(textField, (key + "," + alternative_autocomplete_keys).split(",")); 596 } else { 597 initAutoCompletionField(textField, key); 598 } 599 if (length != null && !length.isEmpty()) { 600 textField.setMaxChars(Integer.valueOf(length)); 601 } 602 if (usage.unused()){ 603 if (auto_increment_selected != 0 && auto_increment != null) { 604 try { 605 textField.setText(Integer.toString(Integer.parseInt(LAST_VALUES.get(key)) + auto_increment_selected)); 606 } catch (NumberFormatException ex) { 607 // Ignore - cannot auto-increment if last was non-numeric 608 } 609 } 610 else if (!usage.hadKeys() || PROP_FILL_DEFAULT.get() || "force".equals(use_last_as_default)) { 611 // selected osm primitives are untagged or filling default values feature is enabled 612 if (!"false".equals(use_last_as_default) && LAST_VALUES.containsKey(key) && !presetInitiallyMatches) { 613 textField.setText(LAST_VALUES.get(key)); 614 } else { 615 textField.setText(default_); 616 } 617 } else { 618 // selected osm primitives are tagged and filling default values feature is disabled 619 textField.setText(""); 620 } 621 value = textField; 622 originalValue = null; 623 } else if (usage.hasUniqueValue()) { 624 // all objects use the same value 625 textField.setText(usage.getFirst()); 626 value = textField; 627 originalValue = usage.getFirst(); 628 } else { 629 // the objects have different values 630 JosmComboBox<String> comboBox = new JosmComboBox<>(usage.values.toArray(new String[0])); 631 comboBox.setEditable(true); 632 comboBox.setEditor(textField); 633 comboBox.getEditor().setItem(DIFFERENT); 634 value=comboBox; 635 originalValue = DIFFERENT; 636 } 637 if (locale_text == null) { 638 if (text != null) { 639 if (text_context != null) { 640 locale_text = trc(text_context, fixPresetString(text)); 641 } else { 642 locale_text = tr(fixPresetString(text)); 643 } 644 } 645 } 646 647 // if there's an auto_increment setting, then wrap the text field 648 // into a panel, appending a number of buttons. 649 // auto_increment has a format like -2,-1,1,2 650 // the text box being the first component in the panel is relied 651 // on in a rather ugly fashion further down. 652 if (auto_increment != null) { 653 ButtonGroup bg = new ButtonGroup(); 654 JPanel pnl = new JPanel(new GridBagLayout()); 655 pnl.add(value, GBC.std().fill(GBC.HORIZONTAL)); 656 657 // first, one button for each auto_increment value 658 for (final String ai : auto_increment.split(",")) { 659 JToggleButton aibutton = new JToggleButton(ai); 660 aibutton.setToolTipText(tr("Select auto-increment of {0} for this field", ai)); 661 aibutton.setMargin(new java.awt.Insets(0,0,0,0)); 662 aibutton.setFocusable(false); 663 bg.add(aibutton); 664 try { 665 // TODO there must be a better way to parse a number like "+3" than this. 666 final int buttonvalue = (NumberFormat.getIntegerInstance().parse(ai.replace("+", ""))).intValue(); 667 if (auto_increment_selected == buttonvalue) aibutton.setSelected(true); 668 aibutton.addActionListener(new ActionListener() { 669 @Override 670 public void actionPerformed(ActionEvent e) { 671 auto_increment_selected = buttonvalue; 672 } 673 }); 674 pnl.add(aibutton, GBC.std()); 675 } catch (ParseException x) { 676 Main.error("Cannot parse auto-increment value of '" + ai + "' into an integer"); 677 } 678 } 679 680 // an invisible toggle button for "release" of the button group 681 final JToggleButton clearbutton = new JToggleButton("X"); 682 clearbutton.setVisible(false); 683 clearbutton.setFocusable(false); 684 bg.add(clearbutton); 685 // and its visible counterpart. - this mechanism allows us to 686 // have *no* button selected after the X is clicked, instead 687 // of the X remaining selected 688 JButton releasebutton = new JButton("X"); 689 releasebutton.setToolTipText(tr("Cancel auto-increment for this field")); 690 releasebutton.setMargin(new java.awt.Insets(0,0,0,0)); 691 releasebutton.setFocusable(false); 692 releasebutton.addActionListener(new ActionListener() { 693 @Override 694 public void actionPerformed(ActionEvent e) { 695 auto_increment_selected = 0; 696 clearbutton.setSelected(true); 697 } 698 }); 699 pnl.add(releasebutton, GBC.eol()); 700 value = pnl; 701 } 702 p.add(new JLabel(locale_text+":"), GBC.std().insets(0,0,10,0)); 703 p.add(value, GBC.eol().fill(GBC.HORIZONTAL)); 704 return true; 705 } 706 707 private static String getValue(Component comp) { 708 if (comp instanceof JosmComboBox) { 709 return ((JosmComboBox<?>) comp).getEditor().getItem().toString(); 710 } else if (comp instanceof JosmTextField) { 711 return ((JosmTextField) comp).getText(); 712 } else if (comp instanceof JPanel) { 713 return getValue(((JPanel)comp).getComponent(0)); 714 } else { 715 return null; 716 } 717 } 718 719 @Override 720 public void addCommands(List<Tag> changedTags) { 721 722 // return if unchanged 723 String v = getValue(value); 724 if (v == null) { 725 Main.error("No 'last value' support for component " + value); 726 return; 727 } 728 729 v = Tag.removeWhiteSpaces(v); 730 731 if (!"false".equals(use_last_as_default) || auto_increment != null) { 732 LAST_VALUES.put(key, v); 733 } 734 if (v.equals(originalValue) || (originalValue == null && v.length() == 0)) 735 return; 736 737 changedTags.add(new Tag(key, v)); 738 } 739 740 @Override 741 boolean requestFocusInWindow() { 742 return value.requestFocusInWindow(); 743 } 744 745 @Override 746 public MatchType getDefaultMatch() { 747 return MatchType.NONE; 748 } 749 750 @Override 751 public Collection<String> getValues() { 752 if (default_ == null || default_.isEmpty()) 753 return Collections.emptyList(); 754 return Collections.singleton(default_); 755 } 756 } 757 758 /** 759 * A group of {@link Check}s. 760 * @since 6114 761 */ 762 public static class CheckGroup extends TaggingPresetItem { 763 764 /** 765 * Number of columns (positive integer) 766 */ 767 public String columns; 768 769 /** 770 * List of checkboxes 771 */ 772 public final List<Check> checks = new LinkedList<>(); 773 774 @Override 775 boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel, boolean presetInitiallyMatches) { 776 Integer cols = Integer.valueOf(columns); 777 int rows = (int) Math.ceil(checks.size()/cols.doubleValue()); 778 JPanel panel = new JPanel(new GridLayout(rows, cols)); 779 780 for (Check check : checks) { 781 check.addToPanel(panel, sel, presetInitiallyMatches); 782 } 783 784 p.add(panel, GBC.eol()); 785 return false; 786 } 787 788 @Override 789 void addCommands(List<Tag> changedTags) { 790 for (Check check : checks) { 791 check.addCommands(changedTags); 792 } 793 } 794 795 @Override 796 public String toString() { 797 return "CheckGroup [columns=" + columns + "]"; 798 } 799 } 800 801 public static class Check extends KeyedItem { 802 803 public String locale_text; 804 public String value_on = OsmUtils.trueval; 805 public String value_off = OsmUtils.falseval; 806 public boolean disable_off = false; 807 public boolean default_ = false; // only used for tagless objects 808 809 private QuadStateCheckBox check; 810 private QuadStateCheckBox.State initialState; 811 private boolean def; 812 813 @Override public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel, boolean presetInitiallyMatches) { 814 815 // find out if our key is already used in the selection. 816 final Usage usage = determineBooleanUsage(sel, key); 817 final String oneValue = usage.values.isEmpty() ? null : usage.values.last(); 818 def = default_; 819 820 if(locale_text == null) { 821 if(text_context != null) { 822 locale_text = trc(text_context, fixPresetString(text)); 823 } else { 824 locale_text = tr(fixPresetString(text)); 825 } 826 } 827 828 if (usage.values.size() < 2 && (oneValue == null || value_on.equals(oneValue) || value_off.equals(oneValue))) { 829 if (def && !PROP_FILL_DEFAULT.get()) { 830 // default is set and filling default values feature is disabled - check if all primitives are untagged 831 for (OsmPrimitive s : sel) 832 if(s.hasKeys()) { 833 def = false; 834 } 835 } 836 837 // all selected objects share the same value which is either true or false or unset, 838 // we can display a standard check box. 839 initialState = value_on.equals(oneValue) 840 ? QuadStateCheckBox.State.SELECTED 841 : value_off.equals(oneValue) 842 ? QuadStateCheckBox.State.NOT_SELECTED 843 : def 844 ? QuadStateCheckBox.State.SELECTED 845 : QuadStateCheckBox.State.UNSET; 846 } else { 847 def = false; 848 // the objects have different values, or one or more objects have something 849 // else than true/false. we display a quad-state check box 850 // in "partial" state. 851 initialState = QuadStateCheckBox.State.PARTIAL; 852 } 853 854 final List<QuadStateCheckBox.State> allowedStates = new ArrayList<>(4); 855 if (QuadStateCheckBox.State.PARTIAL.equals(initialState)) 856 allowedStates.add(QuadStateCheckBox.State.PARTIAL); 857 allowedStates.add(QuadStateCheckBox.State.SELECTED); 858 if (!disable_off || value_off.equals(oneValue)) 859 allowedStates.add(QuadStateCheckBox.State.NOT_SELECTED); 860 allowedStates.add(QuadStateCheckBox.State.UNSET); 861 check = new QuadStateCheckBox(locale_text, initialState, 862 allowedStates.toArray(new QuadStateCheckBox.State[allowedStates.size()])); 863 864 p.add(check, GBC.eol().fill(GBC.HORIZONTAL)); 865 return true; 866 } 867 868 @Override public void addCommands(List<Tag> changedTags) { 869 // if the user hasn't changed anything, don't create a command. 870 if (check.getState() == initialState && !def) return; 871 872 // otherwise change things according to the selected value. 873 changedTags.add(new Tag(key, 874 check.getState() == QuadStateCheckBox.State.SELECTED ? value_on : 875 check.getState() == QuadStateCheckBox.State.NOT_SELECTED ? value_off : 876 null)); 877 } 878 @Override boolean requestFocusInWindow() {return check.requestFocusInWindow();} 879 880 @Override 881 public MatchType getDefaultMatch() { 882 return MatchType.NONE; 883 } 884 885 @Override 886 public Collection<String> getValues() { 887 return disable_off ? Arrays.asList(value_on) : Arrays.asList(value_on, value_off); 888 } 889 890 @Override 891 public String toString() { 892 return "Check [" 893 + (locale_text != null ? "locale_text=" + locale_text + ", " : "") 894 + (value_on != null ? "value_on=" + value_on + ", " : "") 895 + (value_off != null ? "value_off=" + value_off + ", " : "") 896 + "default_=" + default_ + ", " 897 + (check != null ? "check=" + check + ", " : "") 898 + (initialState != null ? "initialState=" + initialState 899 + ", " : "") + "def=" + def + "]"; 900 } 901 } 902 903 public abstract static class ComboMultiSelect extends KeyedItem { 904 905 public String locale_text; 906 public String values; 907 public String values_from; 908 public String values_context; 909 public String display_values; 910 public String locale_display_values; 911 public String short_descriptions; 912 public String locale_short_descriptions; 913 public String default_; 914 public String delimiter = ";"; 915 public String use_last_as_default = "false"; 916 /** whether to use values for search via {@link TaggingPresetSelector} */ 917 public String values_searchable = "false"; 918 919 protected JComponent component; 920 protected final Map<String, PresetListEntry> lhm = new LinkedHashMap<>(); 921 private boolean initialized = false; 922 protected Usage usage; 923 protected Object originalValue; 924 925 protected abstract Object getSelectedItem(); 926 protected abstract void addToPanelAnchor(JPanel p, String def, boolean presetInitiallyMatches); 927 928 protected char getDelChar() { 929 return delimiter.isEmpty() ? ';' : delimiter.charAt(0); 930 } 931 932 @Override 933 public Collection<String> getValues() { 934 initListEntries(); 935 return lhm.keySet(); 936 } 937 938 public Collection<String> getDisplayValues() { 939 initListEntries(); 940 return Utils.transform(lhm.values(), new Utils.Function<PresetListEntry, String>() { 941 @Override 942 public String apply(PresetListEntry x) { 943 return x.getDisplayValue(true); 944 } 945 }); 946 } 947 948 @Override 949 public boolean addToPanel(JPanel p, Collection<OsmPrimitive> sel, boolean presetInitiallyMatches) { 950 951 initListEntries(); 952 953 // find out if our key is already used in the selection. 954 usage = determineTextUsage(sel, key); 955 if (!usage.hasUniqueValue() && !usage.unused()) { 956 lhm.put(DIFFERENT, new PresetListEntry(DIFFERENT)); 957 } 958 959 p.add(new JLabel(tr("{0}:", locale_text)), GBC.std().insets(0, 0, 10, 0)); 960 addToPanelAnchor(p, default_, presetInitiallyMatches); 961 962 return true; 963 964 } 965 966 private void initListEntries() { 967 if (initialized) { 968 lhm.remove(DIFFERENT); // possibly added in #addToPanel 969 return; 970 } else if (lhm.isEmpty()) { 971 initListEntriesFromAttributes(); 972 } else { 973 if (values != null) { 974 Main.warn(tr("Warning in tagging preset \"{0}-{1}\": " 975 + "Ignoring ''{2}'' attribute as ''{3}'' elements are given.", 976 key, text, "values", "list_entry")); 977 } 978 if (display_values != null || locale_display_values != null) { 979 Main.warn(tr("Warning in tagging preset \"{0}-{1}\": " 980 + "Ignoring ''{2}'' attribute as ''{3}'' elements are given.", 981 key, text, "display_values", "list_entry")); 982 } 983 if (short_descriptions != null || locale_short_descriptions != null) { 984 Main.warn(tr("Warning in tagging preset \"{0}-{1}\": " 985 + "Ignoring ''{2}'' attribute as ''{3}'' elements are given.", 986 key, text, "short_descriptions", "list_entry")); 987 } 988 for (PresetListEntry e : lhm.values()) { 989 if (e.value_context == null) { 990 e.value_context = values_context; 991 } 992 } 993 } 994 if (locale_text == null) { 995 locale_text = trc(text_context, fixPresetString(text)); 996 } 997 initialized = true; 998 } 999 1000 private String[] initListEntriesFromAttributes() { 1001 char delChar = getDelChar(); 1002 1003 String[] value_array = null; 1004 1005 if (values_from != null) { 1006 String[] class_method = values_from.split("#"); 1007 if (class_method != null && class_method.length == 2) { 1008 try { 1009 Method method = Class.forName(class_method[0]).getMethod(class_method[1]); 1010 // Check method is public static String[] methodName() 1011 int mod = method.getModifiers(); 1012 if (Modifier.isPublic(mod) && Modifier.isStatic(mod) 1013 && method.getReturnType().equals(String[].class) && method.getParameterTypes().length == 0) { 1014 value_array = (String[]) method.invoke(null); 1015 } else { 1016 Main.error(tr("Broken tagging preset \"{0}-{1}\" - Java method given in ''values_from'' is not \"{2}\"", key, text, 1017 "public static String[] methodName()")); 1018 } 1019 } catch (Exception e) { 1020 Main.error(tr("Broken tagging preset \"{0}-{1}\" - Java method given in ''values_from'' threw {2} ({3})", key, text, 1021 e.getClass().getName(), e.getMessage())); 1022 } 1023 } 1024 } 1025 1026 if (value_array == null) { 1027 value_array = splitEscaped(delChar, values); 1028 } 1029 1030 final String displ = Utils.firstNonNull(locale_display_values, display_values); 1031 String[] display_array = displ == null ? value_array : splitEscaped(delChar, displ); 1032 1033 final String descr = Utils.firstNonNull(locale_short_descriptions, short_descriptions); 1034 String[] short_descriptions_array = descr == null ? null : splitEscaped(delChar, descr); 1035 1036 if (display_array.length != value_array.length) { 1037 Main.error(tr("Broken tagging preset \"{0}-{1}\" - number of items in ''display_values'' must be the same as in ''values''", key, text)); 1038 display_array = value_array; 1039 } 1040 1041 if (short_descriptions_array != null && short_descriptions_array.length != value_array.length) { 1042 Main.error(tr("Broken tagging preset \"{0}-{1}\" - number of items in ''short_descriptions'' must be the same as in ''values''", key, text)); 1043 short_descriptions_array = null; 1044 } 1045 1046 for (int i = 0; i < value_array.length; i++) { 1047 final PresetListEntry e = new PresetListEntry(value_array[i]); 1048 e.locale_display_value = locale_display_values != null 1049 ? display_array[i] 1050 : trc(values_context, fixPresetString(display_array[i])); 1051 if (short_descriptions_array != null) { 1052 e.locale_short_description = locale_short_descriptions != null 1053 ? short_descriptions_array[i] 1054 : tr(fixPresetString(short_descriptions_array[i])); 1055 } 1056 lhm.put(value_array[i], e); 1057 display_array[i] = e.getDisplayValue(true); 1058 } 1059 1060 return display_array; 1061 } 1062 1063 protected String getDisplayIfNull() { 1064 return null; 1065 } 1066 1067 @Override 1068 public void addCommands(List<Tag> changedTags) { 1069 Object obj = getSelectedItem(); 1070 String display = (obj == null) ? null : obj.toString(); 1071 String value = null; 1072 if (display == null) { 1073 display = getDisplayIfNull(); 1074 } 1075 1076 if (display != null) { 1077 for (String val : lhm.keySet()) { 1078 String k = lhm.get(val).toString(); 1079 if (k != null && k.equals(display)) { 1080 value = val; 1081 break; 1082 } 1083 } 1084 if (value == null) { 1085 value = display; 1086 } 1087 } else { 1088 value = ""; 1089 } 1090 value = Tag.removeWhiteSpaces(value); 1091 1092 // no change if same as before 1093 if (originalValue == null) { 1094 if (value.length() == 0) 1095 return; 1096 } else if (value.equals(originalValue.toString())) 1097 return; 1098 1099 if (!"false".equals(use_last_as_default)) { 1100 LAST_VALUES.put(key, value); 1101 } 1102 changedTags.add(new Tag(key, value)); 1103 } 1104 1105 public void addListEntry(PresetListEntry e) { 1106 lhm.put(e.value, e); 1107 } 1108 1109 public void addListEntries(Collection<PresetListEntry> e) { 1110 for (PresetListEntry i : e) { 1111 addListEntry(i); 1112 } 1113 } 1114 1115 @Override 1116 boolean requestFocusInWindow() { 1117 return component.requestFocusInWindow(); 1118 } 1119 1120 private static final ListCellRenderer<PresetListEntry> RENDERER = new ListCellRenderer<PresetListEntry>() { 1121 1122 JLabel lbl = new JLabel(); 1123 1124 @Override 1125 public Component getListCellRendererComponent( 1126 JList<? extends PresetListEntry> list, 1127 PresetListEntry item, 1128 int index, 1129 boolean isSelected, 1130 boolean cellHasFocus) { 1131 1132 // Only return cached size, item is not shown 1133 if (!list.isShowing() && item.prefferedWidth != -1 && item.prefferedHeight != -1) { 1134 if (index == -1) { 1135 lbl.setPreferredSize(new Dimension(item.prefferedWidth, 10)); 1136 } else { 1137 lbl.setPreferredSize(new Dimension(item.prefferedWidth, item.prefferedHeight)); 1138 } 1139 return lbl; 1140 } 1141 1142 lbl.setPreferredSize(null); 1143 1144 1145 if (isSelected) { 1146 lbl.setBackground(list.getSelectionBackground()); 1147 lbl.setForeground(list.getSelectionForeground()); 1148 } else { 1149 lbl.setBackground(list.getBackground()); 1150 lbl.setForeground(list.getForeground()); 1151 } 1152 1153 lbl.setOpaque(true); 1154 lbl.setFont(lbl.getFont().deriveFont(Font.PLAIN)); 1155 lbl.setText("<html>" + item.getListDisplay() + "</html>"); 1156 lbl.setIcon(item.getIcon()); 1157 lbl.setEnabled(list.isEnabled()); 1158 1159 // Cache size 1160 item.prefferedWidth = lbl.getPreferredSize().width; 1161 item.prefferedHeight = lbl.getPreferredSize().height; 1162 1163 // We do not want the editor to have the maximum height of all 1164 // entries. Return a dummy with bogus height. 1165 if (index == -1) { 1166 lbl.setPreferredSize(new Dimension(lbl.getPreferredSize().width, 10)); 1167 } 1168 return lbl; 1169 } 1170 }; 1171 1172 protected ListCellRenderer<PresetListEntry> getListCellRenderer() { 1173 return RENDERER; 1174 } 1175 1176 @Override 1177 public MatchType getDefaultMatch() { 1178 return MatchType.NONE; 1179 } 1180 } 1181 1182 public static class Combo extends ComboMultiSelect { 1183 1184 public boolean editable = true; 1185 protected JosmComboBox<PresetListEntry> combo; 1186 public String length; 1187 1188 /** 1189 * Constructs a new {@code Combo}. 1190 */ 1191 public Combo() { 1192 delimiter = ","; 1193 } 1194 1195 @Override 1196 protected void addToPanelAnchor(JPanel p, String def, boolean presetInitiallyMatches) { 1197 if (!usage.unused()) { 1198 for (String s : usage.values) { 1199 if (!lhm.containsKey(s)) { 1200 lhm.put(s, new PresetListEntry(s)); 1201 } 1202 } 1203 } 1204 if (def != null && !lhm.containsKey(def)) { 1205 lhm.put(def, new PresetListEntry(def)); 1206 } 1207 lhm.put("", new PresetListEntry("")); 1208 1209 combo = new JosmComboBox<>(lhm.values().toArray(new PresetListEntry[0])); 1210 component = combo; 1211 combo.setRenderer(getListCellRenderer()); 1212 combo.setEditable(editable); 1213 combo.reinitialize(lhm.values()); 1214 AutoCompletingTextField tf = new AutoCompletingTextField(); 1215 initAutoCompletionField(tf, key); 1216 if (length != null && !length.isEmpty()) { 1217 tf.setMaxChars(Integer.valueOf(length)); 1218 } 1219 AutoCompletionList acList = tf.getAutoCompletionList(); 1220 if (acList != null) { 1221 acList.add(getDisplayValues(), AutoCompletionItemPriority.IS_IN_STANDARD); 1222 } 1223 combo.setEditor(tf); 1224 1225 if (usage.hasUniqueValue()) { 1226 // all items have the same value (and there were no unset items) 1227 originalValue = lhm.get(usage.getFirst()); 1228 combo.setSelectedItem(originalValue); 1229 } else if (def != null && usage.unused()) { 1230 // default is set and all items were unset 1231 if (!usage.hadKeys() || PROP_FILL_DEFAULT.get() || "force".equals(use_last_as_default)) { 1232 // selected osm primitives are untagged or filling default feature is enabled 1233 combo.setSelectedItem(lhm.get(def).getDisplayValue(true)); 1234 } else { 1235 // selected osm primitives are tagged and filling default feature is disabled 1236 combo.setSelectedItem(""); 1237 } 1238 originalValue = lhm.get(DIFFERENT); 1239 } else if (usage.unused()) { 1240 // all items were unset (and so is default) 1241 originalValue = lhm.get(""); 1242 if ("force".equals(use_last_as_default) && LAST_VALUES.containsKey(key) && !presetInitiallyMatches) { 1243 combo.setSelectedItem(lhm.get(LAST_VALUES.get(key))); 1244 } else { 1245 combo.setSelectedItem(originalValue); 1246 } 1247 } else { 1248 originalValue = lhm.get(DIFFERENT); 1249 combo.setSelectedItem(originalValue); 1250 } 1251 p.add(combo, GBC.eol().fill(GBC.HORIZONTAL)); 1252 1253 } 1254 1255 @Override 1256 protected Object getSelectedItem() { 1257 return combo.getSelectedItem(); 1258 1259 } 1260 1261 @Override 1262 protected String getDisplayIfNull() { 1263 if (combo.isEditable()) 1264 return combo.getEditor().getItem().toString(); 1265 else 1266 return null; 1267 } 1268 } 1269 public static class MultiSelect extends ComboMultiSelect { 1270 1271 public long rows = -1; 1272 protected ConcatenatingJList list; 1273 1274 @Override 1275 protected void addToPanelAnchor(JPanel p, String def, boolean presetInitiallyMatches) { 1276 list = new ConcatenatingJList(delimiter, lhm.values().toArray(new PresetListEntry[0])); 1277 component = list; 1278 ListCellRenderer<PresetListEntry> renderer = getListCellRenderer(); 1279 list.setCellRenderer(renderer); 1280 1281 if (usage.hasUniqueValue() && !usage.unused()) { 1282 originalValue = usage.getFirst(); 1283 list.setSelectedItem(originalValue); 1284 } else if (def != null && !usage.hadKeys() || PROP_FILL_DEFAULT.get() || "force".equals(use_last_as_default)) { 1285 originalValue = DIFFERENT; 1286 list.setSelectedItem(def); 1287 } else if (usage.unused()) { 1288 originalValue = null; 1289 list.setSelectedItem(originalValue); 1290 } else { 1291 originalValue = DIFFERENT; 1292 list.setSelectedItem(originalValue); 1293 } 1294 1295 JScrollPane sp = new JScrollPane(list); 1296 // if a number of rows has been specified in the preset, 1297 // modify preferred height of scroll pane to match that row count. 1298 if (rows != -1) { 1299 double height = renderer.getListCellRendererComponent(list, 1300 new PresetListEntry("x"), 0, false, false).getPreferredSize().getHeight() * rows; 1301 sp.setPreferredSize(new Dimension((int) sp.getPreferredSize().getWidth(), (int) height)); 1302 } 1303 p.add(sp, GBC.eol().fill(GBC.HORIZONTAL)); 1304 1305 1306 } 1307 1308 @Override 1309 protected Object getSelectedItem() { 1310 return list.getSelectedItem(); 1311 } 1312 1313 @Override 1314 public void addCommands(List<Tag> changedTags) { 1315 // Do not create any commands if list has been disabled because of an unknown value (fix #8605) 1316 if (list.isEnabled()) { 1317 super.addCommands(changedTags); 1318 } 1319 } 1320 } 1321 1322 /** 1323 * Class that allows list values to be assigned and retrieved as a comma-delimited 1324 * string (extracted from TaggingPreset) 1325 */ 1326 private static class ConcatenatingJList extends JList<PresetListEntry> { 1327 private String delimiter; 1328 public ConcatenatingJList(String del, PresetListEntry[] o) { 1329 super(o); 1330 delimiter = del; 1331 } 1332 1333 public void setSelectedItem(Object o) { 1334 if (o == null) { 1335 clearSelection(); 1336 } else { 1337 String s = o.toString(); 1338 TreeSet<String> parts = new TreeSet<>(Arrays.asList(s.split(delimiter))); 1339 ListModel<PresetListEntry> lm = getModel(); 1340 int[] intParts = new int[lm.getSize()]; 1341 int j = 0; 1342 for (int i = 0; i < lm.getSize(); i++) { 1343 if (parts.contains((lm.getElementAt(i).value))) { 1344 intParts[j++]=i; 1345 } 1346 } 1347 setSelectedIndices(Arrays.copyOf(intParts, j)); 1348 // check if we have actually managed to represent the full 1349 // value with our presets. if not, cop out; we will not offer 1350 // a selection list that threatens to ruin the value. 1351 setEnabled(Utils.join(delimiter, parts).equals(getSelectedItem())); 1352 } 1353 } 1354 1355 public String getSelectedItem() { 1356 ListModel<PresetListEntry> lm = getModel(); 1357 int[] si = getSelectedIndices(); 1358 StringBuilder builder = new StringBuilder(); 1359 for (int i=0; i<si.length; i++) { 1360 if (i>0) { 1361 builder.append(delimiter); 1362 } 1363 builder.append(lm.getElementAt(si[i]).value); 1364 } 1365 return builder.toString(); 1366 } 1367 } 1368 1369 public static EnumSet<TaggingPresetType> getType(String types) throws SAXException { 1370 if (TYPE_CACHE.containsKey(types)) 1371 return TYPE_CACHE.get(types); 1372 EnumSet<TaggingPresetType> result = EnumSet.noneOf(TaggingPresetType.class); 1373 for (String type : Arrays.asList(types.split(","))) { 1374 try { 1375 TaggingPresetType presetType = TaggingPresetType.fromString(type); 1376 result.add(presetType); 1377 } catch (IllegalArgumentException e) { 1378 throw new SAXException(tr("Unknown type: {0}", type), e); 1379 } 1380 } 1381 TYPE_CACHE.put(types, result); 1382 return result; 1383 } 1384 1385 static String fixPresetString(String s) { 1386 return s == null ? s : s.replaceAll("'","''"); 1387 } 1388 1389 /** 1390 * allow escaped comma in comma separated list: 1391 * "A\, B\, C,one\, two" --> ["A, B, C", "one, two"] 1392 * @param delimiter the delimiter, e.g. a comma. separates the entries and 1393 * must be escaped within one entry 1394 * @param s the string 1395 */ 1396 private static String[] splitEscaped(char delimiter, String s) { 1397 if (s == null) 1398 return new String[0]; 1399 List<String> result = new ArrayList<>(); 1400 boolean backslash = false; 1401 StringBuilder item = new StringBuilder(); 1402 for (int i=0; i<s.length(); i++) { 1403 char ch = s.charAt(i); 1404 if (backslash) { 1405 item.append(ch); 1406 backslash = false; 1407 } else if (ch == '\\') { 1408 backslash = true; 1409 } else if (ch == delimiter) { 1410 result.add(item.toString()); 1411 item.setLength(0); 1412 } else { 1413 item.append(ch); 1414 } 1415 } 1416 if (item.length() > 0) { 1417 result.add(item.toString()); 1418 } 1419 return result.toArray(new String[result.size()]); 1420 } 1421 1422 1423 static Usage determineTextUsage(Collection<OsmPrimitive> sel, String key) { 1424 Usage returnValue = new Usage(); 1425 returnValue.values = new TreeSet<>(); 1426 for (OsmPrimitive s : sel) { 1427 String v = s.get(key); 1428 if (v != null) { 1429 returnValue.values.add(v); 1430 } else { 1431 returnValue.hadEmpty = true; 1432 } 1433 if(s.hasKeys()) { 1434 returnValue.hadKeys = true; 1435 } 1436 } 1437 return returnValue; 1438 } 1439 static Usage determineBooleanUsage(Collection<OsmPrimitive> sel, String key) { 1440 1441 Usage returnValue = new Usage(); 1442 returnValue.values = new TreeSet<>(); 1443 for (OsmPrimitive s : sel) { 1444 String booleanValue = OsmUtils.getNamedOsmBoolean(s.get(key)); 1445 if (booleanValue != null) { 1446 returnValue.values.add(booleanValue); 1447 } 1448 } 1449 return returnValue; 1450 } 1451 protected static ImageIcon loadImageIcon(String iconName, File zipIcons, Integer maxSize) { 1452 final Collection<String> s = Main.pref.getCollection("taggingpreset.icon.sources", null); 1453 ImageProvider imgProv = new ImageProvider(iconName).setDirs(s).setId("presets").setArchive(zipIcons).setOptional(true); 1454 if (maxSize != null) { 1455 imgProv.setMaxSize(maxSize); 1456 } 1457 return imgProv.get(); 1458 } 1459}