001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.tagging.presets; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005import static org.openstreetmap.josm.tools.I18n.trc; 006import static org.openstreetmap.josm.tools.I18n.trn; 007 008import java.awt.Component; 009import java.awt.Dimension; 010import java.awt.GridBagLayout; 011import java.awt.Insets; 012import java.awt.event.ActionEvent; 013import java.io.File; 014import java.util.ArrayList; 015import java.util.Collection; 016import java.util.Collections; 017import java.util.EnumSet; 018import java.util.HashSet; 019import java.util.LinkedList; 020import java.util.List; 021import java.util.Map; 022import java.util.Set; 023import java.util.function.Predicate; 024 025import javax.swing.AbstractAction; 026import javax.swing.Action; 027import javax.swing.ImageIcon; 028import javax.swing.JLabel; 029import javax.swing.JOptionPane; 030import javax.swing.JPanel; 031import javax.swing.JToggleButton; 032import javax.swing.SwingUtilities; 033 034import org.openstreetmap.josm.Main; 035import org.openstreetmap.josm.actions.AdaptableAction; 036import org.openstreetmap.josm.command.ChangePropertyCommand; 037import org.openstreetmap.josm.command.Command; 038import org.openstreetmap.josm.command.SequenceCommand; 039import org.openstreetmap.josm.data.osm.DataSet; 040import org.openstreetmap.josm.data.osm.IPrimitive; 041import org.openstreetmap.josm.data.osm.OsmData; 042import org.openstreetmap.josm.data.osm.OsmPrimitive; 043import org.openstreetmap.josm.data.osm.Relation; 044import org.openstreetmap.josm.data.osm.RelationMember; 045import org.openstreetmap.josm.data.osm.Tag; 046import org.openstreetmap.josm.data.osm.search.SearchCompiler; 047import org.openstreetmap.josm.data.osm.search.SearchCompiler.Match; 048import org.openstreetmap.josm.data.osm.search.SearchParseError; 049import org.openstreetmap.josm.gui.ExtendedDialog; 050import org.openstreetmap.josm.gui.MainApplication; 051import org.openstreetmap.josm.gui.Notification; 052import org.openstreetmap.josm.gui.dialogs.relation.RelationEditor; 053import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeEvent; 054import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener; 055import org.openstreetmap.josm.gui.preferences.ToolbarPreferences; 056import org.openstreetmap.josm.gui.tagging.presets.items.Key; 057import org.openstreetmap.josm.gui.tagging.presets.items.Link; 058import org.openstreetmap.josm.gui.tagging.presets.items.Optional; 059import org.openstreetmap.josm.gui.tagging.presets.items.PresetLink; 060import org.openstreetmap.josm.gui.tagging.presets.items.Roles; 061import org.openstreetmap.josm.gui.tagging.presets.items.Roles.Role; 062import org.openstreetmap.josm.gui.tagging.presets.items.Space; 063import org.openstreetmap.josm.gui.util.GuiHelper; 064import org.openstreetmap.josm.spi.preferences.Config; 065import org.openstreetmap.josm.tools.GBC; 066import org.openstreetmap.josm.tools.ImageProvider; 067import org.openstreetmap.josm.tools.Logging; 068import org.openstreetmap.josm.tools.Utils; 069import org.openstreetmap.josm.tools.template_engine.ParseError; 070import org.openstreetmap.josm.tools.template_engine.TemplateEntry; 071import org.openstreetmap.josm.tools.template_engine.TemplateParser; 072import org.xml.sax.SAXException; 073 074/** 075 * This class read encapsulate one tagging preset. A class method can 076 * read in all predefined presets, either shipped with JOSM or that are 077 * in the config directory. 078 * 079 * It is also able to construct dialogs out of preset definitions. 080 * @since 294 081 */ 082public class TaggingPreset extends AbstractAction implements ActiveLayerChangeListener, AdaptableAction, Predicate<IPrimitive> { 083 084 public static final int DIALOG_ANSWER_APPLY = 1; 085 public static final int DIALOG_ANSWER_NEW_RELATION = 2; 086 public static final int DIALOG_ANSWER_CANCEL = 3; 087 088 public static final String OPTIONAL_TOOLTIP_TEXT = "Optional tooltip text"; 089 090 /** Prefix of preset icon loading failure error message */ 091 public static final String PRESET_ICON_ERROR_MSG_PREFIX = "Could not get presets icon "; 092 093 /** 094 * The preset group this preset belongs to. 095 */ 096 public TaggingPresetMenu group; 097 098 /** 099 * The name of the tagging preset. 100 * @see #getRawName() 101 */ 102 public String name; 103 /** 104 * The icon name assigned to this preset. 105 */ 106 public String iconName; 107 public String name_context; 108 /** 109 * A cache for the local name. Should never be accessed directly. 110 * @see #getLocaleName() 111 */ 112 public String locale_name; 113 public boolean preset_name_label; 114 115 /** 116 * The types as preparsed collection. 117 */ 118 public transient Set<TaggingPresetType> types; 119 public final transient List<TaggingPresetItem> data = new LinkedList<>(); 120 public transient Roles roles; 121 public transient TemplateEntry nameTemplate; 122 public transient Match nameTemplateFilter; 123 124 /** 125 * True whenever the original selection given into createSelection was empty 126 */ 127 private boolean originalSelectionEmpty; 128 129 /** 130 * Create an empty tagging preset. This will not have any items and 131 * will be an empty string as text. createPanel will return null. 132 * Use this as default item for "do not select anything". 133 */ 134 public TaggingPreset() { 135 MainApplication.getLayerManager().addActiveLayerChangeListener(this); 136 updateEnabledState(); 137 } 138 139 /** 140 * Change the display name without changing the toolbar value. 141 */ 142 public void setDisplayName() { 143 putValue(Action.NAME, getName()); 144 putValue("toolbar", "tagging_" + getRawName()); 145 putValue(OPTIONAL_TOOLTIP_TEXT, group != null ? 146 tr("Use preset ''{0}'' of group ''{1}''", getLocaleName(), group.getName()) : 147 tr("Use preset ''{0}''", getLocaleName())); 148 } 149 150 /** 151 * Gets the localized version of the name 152 * @return The name that should be displayed to the user. 153 */ 154 public String getLocaleName() { 155 if (locale_name == null) { 156 if (name_context != null) { 157 locale_name = trc(name_context, TaggingPresetItem.fixPresetString(name)); 158 } else { 159 locale_name = tr(TaggingPresetItem.fixPresetString(name)); 160 } 161 } 162 return locale_name; 163 } 164 165 /** 166 * Returns the translated name of this preset, prefixed with the group names it belongs to. 167 * @return the translated name of this preset, prefixed with the group names it belongs to 168 */ 169 public String getName() { 170 return group != null ? group.getName() + '/' + getLocaleName() : getLocaleName(); 171 } 172 173 /** 174 * Returns the non translated name of this preset, prefixed with the (non translated) group names it belongs to. 175 * @return the non translated name of this preset, prefixed with the (non translated) group names it belongs to 176 */ 177 public String getRawName() { 178 return group != null ? group.getRawName() + '/' + name : name; 179 } 180 181 /** 182 * Returns the preset icon (16px). 183 * @return The preset icon, or {@code null} if none defined 184 * @since 6403 185 */ 186 public final ImageIcon getIcon() { 187 return getIcon(Action.SMALL_ICON); 188 } 189 190 /** 191 * Returns the preset icon (16 or 24px). 192 * @param key Key determining icon size: {@code Action.SMALL_ICON} for 16x, {@code Action.LARGE_ICON_KEY} for 24px 193 * @return The preset icon, or {@code null} if none defined 194 * @since 10849 195 */ 196 public final ImageIcon getIcon(String key) { 197 Object icon = getValue(key); 198 if (icon instanceof ImageIcon) { 199 return (ImageIcon) icon; 200 } 201 return null; 202 } 203 204 /** 205 * Called from the XML parser to set the icon. 206 * The loading task is performed in the background in order to speedup startup. 207 * @param iconName icon name 208 */ 209 public void setIcon(final String iconName) { 210 this.iconName = iconName; 211 if (!TaggingPresetReader.isLoadIcons()) { 212 return; 213 } 214 File arch = TaggingPresetReader.getZipIcons(); 215 final Collection<String> s = Config.getPref().getList("taggingpreset.icon.sources", null); 216 ImageProvider imgProv = new ImageProvider(iconName); 217 imgProv.setDirs(s); 218 imgProv.setId("presets"); 219 imgProv.setArchive(arch); 220 imgProv.setOptional(true); 221 imgProv.getResourceAsync(result -> { 222 if (result != null) { 223 GuiHelper.runInEDT(() -> result.attachImageIcon(this)); 224 } else { 225 Logging.warn(toString() + ": " + PRESET_ICON_ERROR_MSG_PREFIX + iconName); 226 } 227 }); 228 } 229 230 /** 231 * Called from the XML parser to set the types this preset affects. 232 * @param types comma-separated primitive types ("node", "way", "relation" or "closedway") 233 * @throws SAXException if any SAX error occurs 234 * @see TaggingPresetType#fromString 235 */ 236 public void setType(String types) throws SAXException { 237 this.types = TaggingPresetItem.getType(types); 238 } 239 240 public void setName_template(String pattern) throws SAXException { 241 try { 242 this.nameTemplate = new TemplateParser(pattern).parse(); 243 } catch (ParseError e) { 244 Logging.error("Error while parsing " + pattern + ": " + e.getMessage()); 245 throw new SAXException(e); 246 } 247 } 248 249 public void setName_template_filter(String filter) throws SAXException { 250 try { 251 this.nameTemplateFilter = SearchCompiler.compile(filter); 252 } catch (SearchParseError e) { 253 Logging.error("Error while parsing" + filter + ": " + e.getMessage()); 254 throw new SAXException(e); 255 } 256 } 257 258 private static class PresetPanel extends JPanel { 259 private boolean hasElements; 260 261 PresetPanel() { 262 super(new GridBagLayout()); 263 } 264 } 265 266 /** 267 * Returns the tags being directly applied (without UI element) by {@link Key} items 268 * 269 * @return a list of tags 270 */ 271 private List<Tag> getDirectlyAppliedTags() { 272 List<Tag> tags = new ArrayList<>(); 273 for (TaggingPresetItem item : data) { 274 if (item instanceof Key) { 275 tags.add(((Key) item).asTag()); 276 } 277 } 278 return tags; 279 } 280 281 /** 282 * Creates a panel for this preset. This includes general information such as name and supported {@link TaggingPresetType types}. 283 * This includes the elements from the individual {@link TaggingPresetItem items}. 284 * 285 * @param selected the selected primitives 286 * @return the newly created panel 287 */ 288 public PresetPanel createPanel(Collection<OsmPrimitive> selected) { 289 PresetPanel p = new PresetPanel(); 290 List<Link> l = new LinkedList<>(); 291 List<PresetLink> presetLink = new LinkedList<>(); 292 293 final JPanel pp = new JPanel(); 294 if (types != null) { 295 for (TaggingPresetType t : types) { 296 JLabel la = new JLabel(ImageProvider.get(t.getIconName())); 297 la.setToolTipText(tr("Elements of type {0} are supported.", tr(t.getName()))); 298 pp.add(la); 299 } 300 } 301 final List<Tag> directlyAppliedTags = getDirectlyAppliedTags(); 302 if (!directlyAppliedTags.isEmpty()) { 303 final JLabel label = new JLabel(ImageProvider.get("pastetags")); 304 label.setToolTipText("<html>" + tr("This preset also sets: {0}", Utils.joinAsHtmlUnorderedList(directlyAppliedTags))); 305 pp.add(label); 306 } 307 final int count = pp.getComponentCount(); 308 if (preset_name_label) { 309 p.add(new JLabel(getIcon(Action.LARGE_ICON_KEY)), GBC.std(0, 0).span(1, count > 0 ? 2 : 1).insets(0, 0, 5, 0)); 310 } 311 if (count > 0) { 312 p.add(pp, GBC.std(1, 0).span(GBC.REMAINDER)); 313 } 314 if (preset_name_label) { 315 p.add(new JLabel(getName()), GBC.std(1, count > 0 ? 1 : 0).insets(5, 0, 0, 0).span(GBC.REMAINDER).fill(GBC.HORIZONTAL)); 316 } 317 318 boolean presetInitiallyMatches = !selected.isEmpty() && selected.stream().allMatch(this); 319 JPanel items = new JPanel(new GridBagLayout()); 320 for (TaggingPresetItem i : data) { 321 if (i instanceof Link) { 322 l.add((Link) i); 323 p.hasElements = true; 324 } else if (i instanceof PresetLink) { 325 presetLink.add((PresetLink) i); 326 } else { 327 if (i.addToPanel(items, selected, presetInitiallyMatches)) { 328 p.hasElements = true; 329 } 330 } 331 } 332 p.add(items, GBC.eol().fill()); 333 if (selected.isEmpty() && !supportsRelation()) { 334 GuiHelper.setEnabledRec(items, false); 335 } 336 337 // add PresetLink 338 if (!presetLink.isEmpty()) { 339 p.add(new JLabel(tr("Edit also …")), GBC.eol().insets(0, 8, 0, 0)); 340 for (PresetLink link : presetLink) { 341 link.addToPanel(p, selected, presetInitiallyMatches); 342 } 343 } 344 345 // add Link 346 for (Link link : l) { 347 link.addToPanel(p, selected, presetInitiallyMatches); 348 } 349 350 // "Add toolbar button" 351 JToggleButton tb = new JToggleButton(new ToolbarButtonAction()); 352 tb.setFocusable(false); 353 p.add(tb, GBC.std(1, 0).anchor(GBC.LINE_END)); 354 return p; 355 } 356 357 /** 358 * Determines whether a dialog can be shown for this preset, i.e., at least one tag can/must be set by the user. 359 * 360 * @return {@code true} if a dialog can be shown for this preset 361 */ 362 public boolean isShowable() { 363 for (TaggingPresetItem i : data) { 364 if (!(i instanceof Optional || i instanceof Space || i instanceof Key)) 365 return true; 366 } 367 return false; 368 } 369 370 public String suggestRoleForOsmPrimitive(OsmPrimitive osm) { 371 if (roles != null && osm != null) { 372 for (Role i : roles.roles) { 373 if (i.memberExpression != null && i.memberExpression.match(osm) 374 && (i.types == null || i.types.isEmpty() || i.types.contains(TaggingPresetType.forPrimitive(osm)))) { 375 return i.key; 376 } 377 } 378 } 379 return null; 380 } 381 382 @Override 383 public void actionPerformed(ActionEvent e) { 384 if (Main.main == null) { 385 return; 386 } 387 DataSet ds = Main.main.getEditDataSet(); 388 Collection<OsmPrimitive> participants = Collections.emptyList(); 389 if (ds != null) { 390 participants = ds.getSelected(); 391 } 392 393 // Display dialog even if no data layer (used by preset-tagging-tester plugin) 394 Collection<OsmPrimitive> sel = createSelection(participants); 395 int answer = showDialog(sel, supportsRelation()); 396 397 if (ds == null) { 398 return; 399 } 400 401 if (!sel.isEmpty() && answer == DIALOG_ANSWER_APPLY) { 402 Command cmd = createCommand(sel, getChangedTags()); 403 if (cmd != null) { 404 MainApplication.undoRedo.add(cmd); 405 } 406 } else if (answer == DIALOG_ANSWER_NEW_RELATION) { 407 final Relation r = new Relation(); 408 final Collection<RelationMember> members = new HashSet<>(); 409 for (Tag t : getChangedTags()) { 410 r.put(t.getKey(), t.getValue()); 411 } 412 for (OsmPrimitive osm : ds.getSelected()) { 413 String role = suggestRoleForOsmPrimitive(osm); 414 RelationMember rm = new RelationMember(role == null ? "" : role, osm); 415 r.addMember(rm); 416 members.add(rm); 417 } 418 SwingUtilities.invokeLater(() -> RelationEditor.getEditor( 419 MainApplication.getLayerManager().getEditLayer(), r, members).setVisible(true)); 420 } 421 ds.setSelected(ds.getSelected()); // force update 422 } 423 424 private static class PresetDialog extends ExtendedDialog { 425 426 /** 427 * Constructs a new {@code PresetDialog}. 428 * @param content the content that will be displayed in this dialog 429 * @param title the text that will be shown in the window titlebar 430 * @param icon the image to be displayed as the icon for this window 431 * @param disableApply whether to disable "Apply" button 432 * @param showNewRelation whether to display "New relation" button 433 */ 434 PresetDialog(Component content, String title, ImageIcon icon, boolean disableApply, boolean showNewRelation) { 435 super(Main.parent, title, 436 showNewRelation ? 437 (new String[] {tr("Apply Preset"), tr("New relation"), tr("Cancel")}) : 438 (new String[] {tr("Apply Preset"), tr("Cancel")}), 439 true); 440 if (icon != null) 441 setIconImage(icon.getImage()); 442 contentInsets = new Insets(10, 5, 0, 5); 443 if (showNewRelation) { 444 setButtonIcons("ok", "dialogs/addrelation", "cancel"); 445 } else { 446 setButtonIcons("ok", "cancel"); 447 } 448 setContent(content); 449 setDefaultButton(1); 450 setupDialog(); 451 buttons.get(0).setEnabled(!disableApply); 452 buttons.get(0).setToolTipText(title); 453 // Prevent dialogs of being too narrow (fix #6261) 454 Dimension d = getSize(); 455 if (d.width < 350) { 456 d.width = 350; 457 setSize(d); 458 } 459 super.showDialog(); 460 } 461 } 462 463 /** 464 * Shows the preset dialog. 465 * @param sel selection 466 * @param showNewRelation whether to display "New relation" button 467 * @return the user choice after the dialog has been closed 468 */ 469 public int showDialog(Collection<OsmPrimitive> sel, boolean showNewRelation) { 470 PresetPanel p = createPanel(sel); 471 472 int answer = 1; 473 boolean canCreateRelation = types == null || types.contains(TaggingPresetType.RELATION); 474 if (originalSelectionEmpty && !canCreateRelation) { 475 new Notification( 476 tr("The preset <i>{0}</i> cannot be applied since nothing has been selected!", getLocaleName())) 477 .setIcon(JOptionPane.WARNING_MESSAGE) 478 .show(); 479 return DIALOG_ANSWER_CANCEL; 480 } else if (sel.isEmpty() && !canCreateRelation) { 481 new Notification( 482 tr("The preset <i>{0}</i> cannot be applied since the selection is unsuitable!", getLocaleName())) 483 .setIcon(JOptionPane.WARNING_MESSAGE) 484 .show(); 485 return DIALOG_ANSWER_CANCEL; 486 } else if (p.getComponentCount() != 0 && (sel.isEmpty() || p.hasElements)) { 487 String title = trn("Change {0} object", "Change {0} objects", sel.size(), sel.size()); 488 if (!showNewRelation && sel.isEmpty()) { 489 if (originalSelectionEmpty) { 490 title = tr("Nothing selected!"); 491 } else { 492 title = tr("Selection unsuitable!"); 493 } 494 } 495 496 boolean disableApply = false; 497 if (!sel.isEmpty()) { 498 OsmData<?, ?, ?, ?> ds = sel.iterator().next().getDataSet(); 499 disableApply = ds != null && ds.isLocked(); 500 } 501 answer = new PresetDialog(p, title, preset_name_label ? null : (ImageIcon) getValue(Action.SMALL_ICON), 502 disableApply, showNewRelation).getValue(); 503 } 504 if (!showNewRelation && answer == 2) 505 return DIALOG_ANSWER_CANCEL; 506 else 507 return answer; 508 } 509 510 /** 511 * Removes all unsuitable OsmPrimitives from the given list 512 * @param participants List of possible OsmPrimitives to tag 513 * @return Cleaned list with suitable OsmPrimitives only 514 */ 515 public Collection<OsmPrimitive> createSelection(Collection<OsmPrimitive> participants) { 516 originalSelectionEmpty = participants.isEmpty(); 517 Collection<OsmPrimitive> sel = new LinkedList<>(); 518 for (OsmPrimitive osm : participants) { 519 if (typeMatches(EnumSet.of(TaggingPresetType.forPrimitive(osm)))) { 520 sel.add(osm); 521 } 522 } 523 return sel; 524 } 525 526 /** 527 * Gets a list of tags that are set by this preset. 528 * @return The list of tags. 529 */ 530 public List<Tag> getChangedTags() { 531 List<Tag> result = new ArrayList<>(); 532 for (TaggingPresetItem i: data) { 533 i.addCommands(result); 534 } 535 return result; 536 } 537 538 /** 539 * Create a command to change the given list of tags. 540 * @param sel The primitives to change the tags for 541 * @param changedTags The tags to change 542 * @return A command that changes the tags. 543 */ 544 public static Command createCommand(Collection<OsmPrimitive> sel, List<Tag> changedTags) { 545 List<Command> cmds = new ArrayList<>(); 546 for (Tag tag: changedTags) { 547 ChangePropertyCommand cmd = new ChangePropertyCommand(sel, tag.getKey(), tag.getValue()); 548 if (cmd.getObjectsNumber() > 0) { 549 cmds.add(cmd); 550 } 551 } 552 553 if (cmds.isEmpty()) 554 return null; 555 else if (cmds.size() == 1) 556 return cmds.get(0); 557 else 558 return new SequenceCommand(tr("Change Tags"), cmds); 559 } 560 561 private boolean supportsRelation() { 562 return types == null || types.contains(TaggingPresetType.RELATION); 563 } 564 565 protected final void updateEnabledState() { 566 setEnabled(Main.main != null && Main.main.getEditDataSet() != null); 567 } 568 569 @Override 570 public void activeOrEditLayerChanged(ActiveLayerChangeEvent e) { 571 updateEnabledState(); 572 } 573 574 @Override 575 public String toString() { 576 return (types == null ? "" : types.toString()) + ' ' + name; 577 } 578 579 /** 580 * Determines whether this preset matches the types. 581 * @param t The types that must match 582 * @return <code>true</code> if all types match. 583 */ 584 public boolean typeMatches(Collection<TaggingPresetType> t) { 585 return t == null || types == null || types.containsAll(t); 586 } 587 588 /** 589 * Determines whether this preset matches the given primitive, i.e., 590 * whether the {@link #typeMatches(Collection) type matches} and the {@link TaggingPresetItem#matches(Map) tags match}. 591 * 592 * @param p the primitive 593 * @return {@code true} if this preset matches the primitive 594 * @since 13623 (signature) 595 */ 596 @Override 597 public boolean test(IPrimitive p) { 598 return matches(EnumSet.of(TaggingPresetType.forPrimitive(p)), p.getKeys(), false); 599 } 600 601 /** 602 * Determines whether this preset matches the parameters. 603 * 604 * @param t the preset types to include, see {@link #typeMatches(Collection)} 605 * @param tags the tags to perform matching on, see {@link TaggingPresetItem#matches(Map)} 606 * @param onlyShowable whether the preset must be {@link #isShowable() showable} 607 * @return {@code true} if this preset matches the parameters. 608 */ 609 public boolean matches(Collection<TaggingPresetType> t, Map<String, String> tags, boolean onlyShowable) { 610 if ((onlyShowable && !isShowable()) || !typeMatches(t)) { 611 return false; 612 } else { 613 return TaggingPresetItem.matches(data, tags); 614 } 615 } 616 617 /** 618 * Action that adds or removes the button on main toolbar 619 */ 620 public class ToolbarButtonAction extends AbstractAction { 621 private final int toolbarIndex; 622 623 /** 624 * Constructs a new {@code ToolbarButtonAction}. 625 */ 626 public ToolbarButtonAction() { 627 super(""); 628 new ImageProvider("dialogs", "pin").getResource().attachImageIcon(this, true); 629 putValue(SHORT_DESCRIPTION, tr("Add or remove toolbar button")); 630 List<String> t = new LinkedList<>(ToolbarPreferences.getToolString()); 631 toolbarIndex = t.indexOf(getToolbarString()); 632 putValue(SELECTED_KEY, toolbarIndex >= 0); 633 } 634 635 @Override 636 public void actionPerformed(ActionEvent ae) { 637 String res = getToolbarString(); 638 MainApplication.getToolbar().addCustomButton(res, toolbarIndex, true); 639 } 640 } 641 642 /** 643 * Gets a string describing this preset that can be used for the toolbar 644 * @return A String that can be passed on to the toolbar 645 * @see ToolbarPreferences#addCustomButton(String, int, boolean) 646 */ 647 public String getToolbarString() { 648 ToolbarPreferences.ActionParser actionParser = new ToolbarPreferences.ActionParser(null); 649 return actionParser.saveAction(new ToolbarPreferences.ActionDefinition(this)); 650 } 651}