001package jmri.jmrit.display; 002 003import java.beans.PropertyChangeEvent; 004import java.beans.PropertyChangeListener; 005import java.util.*; 006import java.util.stream.Collectors; 007 008import javax.annotation.CheckForNull; 009import javax.annotation.Nonnull; 010import jmri.InstanceManager; 011import jmri.InstanceManagerAutoDefault; 012import jmri.beans.Bean; 013 014/** 015 * Manager for JMRI Editors. This manager tracks editors, extending the Set 016 * interface to do so (so it can be interacted with as a normal set), while also 017 * providing some methods specific to editors. 018 * <p> 019 * This manager listens to the {@code title} property of Editors to be notified 020 * to changes to the title of the Editor that could affect the order of editors. 021 * <p> 022 * This manager generates an {@link java.beans.IndexedPropertyChangeEvent} for 023 * the property named {@code editors} when an editor is added or removed and 024 * forwards the {@link java.beans.PropertyChangeEvent} for the {@code title} 025 * property of Editors in the manager. 026 * 027 * @author Randall Wood Copyright 2020 028 */ 029public class EditorManager extends Bean implements PropertyChangeListener, InstanceManagerAutoDefault { 030 031 public static final String EDITORS = "editors"; 032 public static final String TITLE = "title"; 033 private final SortedSet<Editor> set = Collections.synchronizedSortedSet(new TreeSet<>(Comparator.comparing(Editor::getTitle))); 034 035 public EditorManager() { 036 super(false); 037 } 038 039 /** 040 * Set the title for the Preferences / Messages tab. 041 * Called by JmriUserPreferencesManager. 042 * @return the title string. 043 */ 044 public String getClassDescription() { 045 return Bundle.getMessage("TitlePanelDialogs"); // NOI18N 046 } 047 048 /** 049 * Set the details for Preferences / Messages tab. 050 * Called by JmriUserPreferencesManager. 051 * <p> 052 * The dialogs are in jmri.configurexml.LoadXmlConfigAction and jmri.jmrit.display.Editor. 053 * They are anchored here since the preferences system appears to need a class that can instantiated. 054 */ 055 public void setMessagePreferencesDetails() { 056 InstanceManager.getDefault(jmri.UserPreferencesManager.class).setPreferenceItemDetails( 057 "jmri.jmrit.display.EditorManager", "skipHideDialog", Bundle.getMessage("PanelHideSkip")); // NOI18N 058 InstanceManager.getDefault(jmri.UserPreferencesManager.class).setPreferenceItemDetails( 059 "jmri.jmrit.display.EditorManager", "skipDupLoadDialog", Bundle.getMessage("DuplicateLoadSkip")); // NOI18N 060 InstanceManager.getDefault(jmri.UserPreferencesManager.class).setPreferenceItemDetails( 061 "jmri.jmrit.display.EditorManager", "skipEntryExitDialog", Bundle.getMessage("EntryExitBlockSkip")); // NOI18N 062 } 063 064 /** 065 * Add an editor to this manager. 066 * 067 * @param editor the editor to add 068 */ 069 public void add(@Nonnull Editor editor) { 070 boolean result = set.add(editor); 071 if (result) { 072 fireIndexedPropertyChange(EDITORS, set.size(), null, editor); 073 editor.addPropertyChangeListener(TITLE, this); 074 } 075 } 076 077 /** 078 * Check if an editor is in the manager. 079 * 080 * @param editor the editor to check for 081 * @return true if this manager contains an editor with name; false 082 * otherwise 083 */ 084 public boolean contains(@Nonnull Editor editor) { 085 return set.contains(editor); 086 } 087 088 /** 089 * Get all managed editors. This set is sorted by the title of the editor. 090 * 091 * @return the set of all editors 092 */ 093 @Nonnull 094 public SortedSet<Editor> getAll() { 095 return new TreeSet<>(set); 096 } 097 098 /** 099 * Get all managed editors that implement the specified type. This set is 100 * sorted by the title of the editor. 101 * 102 * @param <T> the specified type 103 * @param type the specified type 104 * @return the set of all editors of the specified type 105 */ 106 @Nonnull 107 public <T extends Editor> SortedSet<T> getAll(@Nonnull Class<T> type) { 108 return set.stream() 109 .filter(e -> type.isAssignableFrom(e.getClass())) 110 .map(type::cast) 111 .collect(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(Editor::getTitle)))); 112 } 113 114 /** 115 * Get the editor with the given title. 116 * 117 * @param title the title of the editor 118 * @return the editor with the given title or null if no editor by that title 119 * exists 120 */ 121 @CheckForNull 122 public Editor get(@Nonnull String title) { 123 return getAll().stream().filter(e -> e.getTitle().equals(title)).findFirst().orElse(null); 124 } 125 126 /** 127 * Get the editor with the given name. 128 * 129 * @param name the name of the editor 130 * @return the editor with the given name or null if no editor by that name 131 * exists 132 */ 133 @CheckForNull 134 public Editor getByName(@Nonnull String name) { 135 return getAll().stream().filter(e -> e.getName().equals(name)).findFirst().orElse(null); 136 } 137 138 /** 139 * Get the editor with the given name or the editor with the given target frame name. 140 * 141 * @param name the name of the editor or target frame 142 * @return the editor or null 143 */ 144 @CheckForNull 145 public Editor getTargetFrame(@Nonnull String name) { 146 Editor editor = get(name); 147 if (editor != null) { 148 return editor; 149 } 150 return getAll().stream().filter(e -> e.getTargetFrame().getTitle().equals(name)).findFirst().orElse(null); 151 } 152 153 /** 154 * Get the editor with the given name and type. 155 * 156 * @param <T> the type of the editor 157 * @param type the type of the editor 158 * @param name the name of the editor 159 * @return the editor with the given name or null if no editor by that name 160 * exists 161 */ 162 @CheckForNull 163 public <T extends Editor> T get(@Nonnull Class<T> type, @Nonnull String name) { 164 return type.cast(set.stream() 165 .filter(e -> e.getClass().isAssignableFrom(type) && e.getTitle().equals(name)) 166 .findFirst().orElse(null)); 167 } 168 169 /** 170 * Remove an editor from this manager. 171 * 172 * @param editor the editor to remove 173 */ 174 public void remove(@Nonnull Editor editor) { 175 boolean result = set.remove(editor); 176 if (result) { 177 fireIndexedPropertyChange(EDITORS, set.size(), editor, null); 178 editor.removePropertyChangeListener(TITLE, this); 179 } 180 } 181 182 @Override 183 public void propertyChange(PropertyChangeEvent evt) { 184 if (evt.getSource() instanceof Editor) { 185 Editor editor = (Editor) evt.getSource(); 186 if (contains(editor) && TITLE.equals(evt.getPropertyName())) { 187 set.remove(editor); 188 set.add(editor); 189 firePropertyChange(evt); 190 } 191 } 192 } 193 194 /** 195 * Check if an editor with the specified name is in the manager. 196 * 197 * @param name the name to check for 198 * @return true if this manager contains an editor with name; false 199 * otherwise 200 */ 201 public boolean contains(String name) { 202 return get(name) != null; 203 } 204 205 /** 206 * Get the set of all Editors as a List. This is a convenience method for 207 * use in scripts. 208 * 209 * @return the set of all Editors 210 */ 211 @Nonnull 212 public List<Editor> getList() { 213 return new ArrayList<>(getAll()); 214 } 215 216 /** 217 * Get the set of all editors that implement the specified type. This is a 218 * convenience method for use in scripts. 219 * 220 * @param <T> the specified type 221 * @param type the specified type 222 * @return the set of all editors that implement the specified type 223 */ 224 @Nonnull 225 public <T extends Editor> List<T> getList(@Nonnull Class<T> type) { 226 return new ArrayList<>(getAll(type)); 227 } 228}