001 /*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2013
005 * Space Science and Engineering Center (SSEC)
006 * University of Wisconsin - Madison
007 * 1225 W. Dayton Street, Madison, WI 53706, USA
008 * https://www.ssec.wisc.edu/mcidas
009 *
010 * All Rights Reserved
011 *
012 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
013 * some McIDAS-V source code is based on IDV and VisAD source code.
014 *
015 * McIDAS-V is free software; you can redistribute it and/or modify
016 * it under the terms of the GNU Lesser Public License as published by
017 * the Free Software Foundation; either version 3 of the License, or
018 * (at your option) any later version.
019 *
020 * McIDAS-V is distributed in the hope that it will be useful,
021 * but WITHOUT ANY WARRANTY; without even the implied warranty of
022 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
023 * GNU Lesser Public License for more details.
024 *
025 * You should have received a copy of the GNU Lesser Public License
026 * along with this program. If not, see http://www.gnu.org/licenses.
027 */
028 package edu.wisc.ssec.mcidasv.servermanager;
029
030 import static edu.wisc.ssec.mcidasv.util.CollectionHelpers.arrList;
031
032 import java.awt.Dimension;
033 import java.awt.Insets;
034 import java.awt.Point;
035 import java.awt.event.ActionEvent;
036 import java.awt.event.ActionListener;
037 import java.util.HashSet;
038 import java.util.LinkedHashMap;
039 import java.util.List;
040 import java.util.Map;
041 import java.util.Set;
042 import java.util.Map.Entry;
043
044 import javax.swing.Icon;
045 import javax.swing.JButton;
046 import javax.swing.JCheckBox;
047 import javax.swing.JLabel;
048 import javax.swing.JPanel;
049 import javax.swing.JRadioButton;
050 import javax.swing.JScrollPane;
051
052 import org.bushe.swing.event.EventBus;
053 import org.bushe.swing.event.annotation.AnnotationProcessor;
054 import org.bushe.swing.event.annotation.EventTopicSubscriber;
055 import org.slf4j.Logger;
056 import org.slf4j.LoggerFactory;
057
058 import ucar.unidata.idv.IdvObjectStore;
059 import ucar.unidata.ui.CheckboxCategoryPanel;
060 import ucar.unidata.util.GuiUtils;
061 import ucar.unidata.xml.PreferenceManager;
062 import ucar.unidata.xml.XmlObjectStore;
063
064 import edu.wisc.ssec.mcidasv.Constants;
065 import edu.wisc.ssec.mcidasv.McIDASV;
066 import edu.wisc.ssec.mcidasv.McIdasPreferenceManager;
067 import edu.wisc.ssec.mcidasv.servermanager.AddeEntry.EntryStatus;
068 import edu.wisc.ssec.mcidasv.servermanager.AddeEntry.EntryType;
069 import edu.wisc.ssec.mcidasv.util.Contract;
070
071 /**
072 * The ADDE Server preference panel is <b>almost</b> a read-only {@literal "view"}
073 * of the current state of the server manager. The only thing that users can
074 * change from here is the visibility of the individual {@link AddeEntry}s,
075 * though there has been some talk of allowing for reordering.
076 */
077 public class AddePreferences {
078
079 public enum Selection { ALL_ENTRIES, SPECIFIED_ENTRIES };
080
081 private static final Logger logger = LoggerFactory.getLogger(AddePreferences.class);
082
083 /**
084 * Property ID for controlling the display of {@literal "site"} servers
085 * in the server preferences.
086 */
087 private static final String PREF_LIST_SITE_SERV = "mcv.servers.listsite";
088
089 /**
090 * Property ID for controlling the display of {@literal "default mcv"}
091 * servers in the server preferences.
092 */
093 private static final String PREF_LIST_DEFAULT_SERV = "mcv.servers.listdefault";
094
095 /**
096 * Property ID for controlling the display of {@literal "MCTABLE"}
097 * servers in the server preferences.
098 */
099 private static final String PREF_LIST_MCTABLE_SERV = "mcv.servers.listmcx";
100
101 /**
102 * Property ID for controlling the display of {@literal "user"} servers
103 * in the server preferences.
104 */
105 private static final String PREF_LIST_USER_SERV = "mcv.servers.listuser";
106
107 /**
108 * Property ID that allows McIDAS-V to remember whether or not the user
109 * has chosen to use all available ADDE servers or has specified the
110 * {@literal "active"} servers.
111 */
112 private static final String PREF_LIST_SPECIFY = "mcv.servers.pref.specify";
113
114 // TODO need to get open/close methods added to CheckboxCategoryPanel
115 // private static final String PREF_LIST_TYPE_PREFIX = "mcv.servers.types.list";
116
117 /** Contains the lists of ADDE servers that we'll use as content. */
118 private final EntryStore entryStore;
119
120 /** Panel that contains the various {@link AddeEntry}s. */
121 private JPanel cbPanel = null;
122
123 private JScrollPane cbScroller = null;
124
125 /**
126 * Allows the user to enable all {@link AddeEntry}s, <b><i>without</i></b>
127 * disabling the preference panel.
128 */
129 private JButton allOn = null;
130
131 /**
132 * Allows the user to disable all {@link AddeEntry}s, <b><i>without</i></b>
133 * disabling the preference panel.
134 */
135 private JButton allOff = null;
136
137 /**
138 * Prepares a new preference panel based upon the supplied
139 * {@link EntryStore}.
140 *
141 * @param entryStore The {@code EntryStore} to query. Cannot be
142 * {@code null}.
143 *
144 * @throws NullPointerException if {@code entryStore} is {@code null}.
145 */
146 public AddePreferences(final EntryStore entryStore) {
147 AnnotationProcessor.process(this);
148 if (entryStore == null) {
149 throw new NullPointerException("EntryStore cannot be null");
150 }
151 this.entryStore = entryStore;
152 }
153
154 /**
155 * Adds the various {@link AddePrefConglomeration} objects to the {@code prefManager}.
156 *
157 * @param prefManager McIDAS-V's {@link PreferenceManager}. Should not be {@code null}.
158 */
159 public void addPanel(McIdasPreferenceManager prefManager) {
160 AddePrefConglomeration notPretty = buildPanel((McIDASV)prefManager.getIdv());
161 // add the panel, listeners, and so on to the preference manager.
162 prefManager.add(notPretty.getName(), "blah", notPretty.getEntryListener(),
163 notPretty.getEntryPanel(), notPretty.getEntryToggles());
164 }
165
166 /**
167 * Listens for {@link ucar.unidata.ui.CheckboxCategoryPanel} updates and
168 * stores the current status.
169 *
170 * @param topic Topic of interest is {@code "CheckboxCategoryPanel.PanelToggled"}.
171 * @param catPanel The object that changed.
172 */
173 @EventTopicSubscriber(topic="CheckboxCategoryPanel.PanelToggled")
174 public void handleCategoryToggle(final String topic, final CheckboxCategoryPanel catPanel) {
175 IdvObjectStore store = entryStore.getIdvStore();
176 store.put("addepref.category."+catPanel.getCategoryName(), catPanel.isOpen());
177 }
178
179 /**
180 * Builds the remote server preference panel, using the given
181 * {@link McIdasPreferenceManager}.
182 *
183 * @param mcv Reference to the McIDAS-V object; mostly used to control the
184 * server manager GUI. Cannot be {@code null}.
185 *
186 * @return An object containing the various components required of a
187 * preference panel.
188 */
189 public AddePrefConglomeration buildPanel(final McIDASV mcv) {
190 Contract.notNull(mcv, "Cannot build a preference panel with a null McIDASV object");
191 Map<EntryType, Set<AddeEntry>> entries =
192 entryStore.getVerifiedEntriesByTypes();
193
194 final Map<AddeEntry, JCheckBox> entryToggles =
195 new LinkedHashMap<AddeEntry, JCheckBox>();
196
197 final List<CheckboxCategoryPanel> typePanels = arrList();
198 List<JPanel> compList = arrList();
199
200 final IdvObjectStore store = mcv.getStore();
201
202 // create checkboxes for each AddeEntry and add 'em to the appropriate
203 // CheckboxCategoryPanel
204 for (final EntryType type : EntryType.values()) {
205 if (EntryType.INVALID.equals(type)) {
206 continue;
207 }
208 final Set<AddeEntry> subset = entries.get(type);
209 Set<String> observedEntries = new HashSet<String>(subset.size());
210 boolean typePanelVis = store.get("addepref.category."+type.toString(), false);
211 final CheckboxCategoryPanel typePanel =
212 new CheckboxCategoryPanel(type.toString(), typePanelVis);
213
214 for (final AddeEntry entry : subset) {
215 final String entryText = entry.getEntryText();
216 if (observedEntries.contains(entryText)) {
217 continue;
218 }
219
220 boolean enabled = (entry.getEntryStatus() == EntryStatus.ENABLED);
221 final JCheckBox cbx = new JCheckBox(entryText, enabled);
222 cbx.addActionListener(new ActionListener() {
223 public void actionPerformed(final ActionEvent e) {
224 EntryStatus status = (cbx.isSelected()) ? EntryStatus.ENABLED : EntryStatus.DISABLED;
225 logger.trace("entry={} val={} status={} evt={}", new Object[] { entryText, cbx.isSelected(), status, e});
226 entry.setEntryStatus(status);
227 entryToggles.put(entry, cbx);
228 store.put("addeprefs.scroller.pos", cbScroller.getViewport().getViewPosition());
229 EventBus.publish(EntryStore.Event.UPDATE);
230 }
231 });
232 entryToggles.put(entry, cbx);
233 typePanel.addItem(cbx);
234 typePanel.add(GuiUtils.inset(cbx, new Insets(0, 20, 0, 0)));
235 observedEntries.add(entryText);
236 }
237
238 compList.add(typePanel.getTopPanel());
239 compList.add(typePanel);
240 typePanels.add(typePanel);
241 typePanel.checkVisCbx();
242 }
243
244 // create the basic pref panel
245 // TODO(jon): determine dimensions more intelligently!
246 cbPanel = GuiUtils.top(GuiUtils.vbox(compList));
247 cbScroller = new JScrollPane(cbPanel);
248 cbScroller.getVerticalScrollBar().setUnitIncrement(10);
249 cbScroller.setPreferredSize(new Dimension(300, 300));
250 Point oldPos = (Point)store.get("addeprefs.scroller.pos");
251 if (oldPos == null) {
252 oldPos = new Point(0,0);
253 }
254 cbScroller.getViewport().setViewPosition(oldPos);
255
256
257 // handle the user opting to enable all servers
258 allOn = new JButton("All on");
259 allOn.addActionListener(new ActionListener() {
260 public void actionPerformed(final ActionEvent e) {
261 for (CheckboxCategoryPanel cbcp : typePanels) {
262 cbcp.toggleAll(true);
263 }
264 }
265 });
266
267 // handle the user opting to disable all servers
268 allOff = new JButton("All off");
269 allOff.addActionListener(new ActionListener() {
270 public void actionPerformed(final ActionEvent e) {
271 for (CheckboxCategoryPanel cbcp : typePanels) {
272 cbcp.toggleAll(false);
273 }
274 }
275 });
276
277 // user wants to add a server! make it so.
278 final JButton addServer = new JButton("Add ADDE Servers...");
279 addServer.addActionListener(new ActionListener() {
280 public void actionPerformed(final ActionEvent e) {
281 mcv.showServerManager();
282 }
283 });
284
285 // import list of servers
286 final JButton importServers = new JButton("Import Servers...");
287 importServers.addActionListener(new ActionListener() {
288 public void actionPerformed(final ActionEvent e) {
289 mcv.showServerManager();
290 }
291 });
292
293 boolean useAll = false;
294 boolean specify = false;
295 if (Selection.ALL_ENTRIES.equals(getSpecifyServers())) {
296 useAll = true;
297 } else {
298 specify = true;
299 }
300
301 // disable user selection of entries--they're using everything
302 final JRadioButton useAllBtn =
303 new JRadioButton("Use all ADDE entries", useAll);
304 useAllBtn.addActionListener(new ActionListener() {
305 public void actionPerformed(ActionEvent ae) {
306 setGUIEnabled(!useAllBtn.isSelected());
307 // TODO(jon): use the eventbus
308 setSpecifyServers(Selection.ALL_ENTRIES);
309 EventBus.publish(EntryStore.Event.UPDATE); // doesn't work...
310 }
311 });
312
313 // let the user specify the "active" set, enable entry selection
314 final JRadioButton useTheseBtn =
315 new JRadioButton("Use selected ADDE entries:", specify);
316 useTheseBtn.addActionListener(new ActionListener() {
317 public void actionPerformed(ActionEvent ae) {
318 setGUIEnabled(!useAllBtn.isSelected());
319 // TODO(jon): use the eventbus
320 setSpecifyServers(Selection.SPECIFIED_ENTRIES);
321 EventBus.publish(EntryStore.Event.UPDATE); // doesn't work...
322 }
323 });
324 GuiUtils.buttonGroup(useAllBtn, useTheseBtn);
325
326 // force the selection state
327 setGUIEnabled(!useAllBtn.isSelected());
328
329 JPanel widgetPanel =
330 GuiUtils.topCenter(
331 GuiUtils.hbox(useAllBtn, useTheseBtn),
332 GuiUtils.leftCenter(
333 GuiUtils.inset(
334 GuiUtils.top(GuiUtils.vbox(allOn, allOff, addServer, importServers)),
335 4), cbScroller));
336
337 JPanel entryPanel =
338 GuiUtils.topCenter(
339 GuiUtils.inset(
340 new JLabel("Specify the active ADDE servers:"),
341 4), widgetPanel);
342 entryPanel = GuiUtils.inset(GuiUtils.left(entryPanel), 6);
343
344 // iterate through all the entries and "apply" any changes:
345 // reordering, removing, visibility changes, etc
346 PreferenceManager entryListener = new PreferenceManager() {
347 public void applyPreference(XmlObjectStore store, Object data) {
348 // logger.trace("well, the damn thing fires at least");
349 // // this won't break because the data parameter is whatever
350 // // has been passed in to "prefManager.add(...)". in this case,
351 // // it's the "entryToggles" variable.
352 store.put("addeprefs.scroller.pos", cbScroller.getViewport().getViewPosition());
353
354 @SuppressWarnings("unchecked")
355 Map<AddeEntry, JCheckBox> toggles = (Map<AddeEntry, JCheckBox>)data;
356 boolean updated = false;
357 for (Entry<AddeEntry, JCheckBox> entry : toggles.entrySet()) {
358 AddeEntry e = entry.getKey();
359 JCheckBox c = entry.getValue();
360 EntryStatus currentStatus = e.getEntryStatus();
361 EntryStatus nextStatus = (c.isSelected()) ? EntryStatus.ENABLED : EntryStatus.DISABLED;
362 logger.trace("entry={} type={} old={} new={}", new Object[] { e, e.getEntryType(), currentStatus, nextStatus });
363 // if (currentStatus != nextStatus) {
364 e.setEntryStatus(nextStatus);
365 toggles.put(e, c);
366 updated = true;
367 // }
368 }
369 if (updated) {
370 EventBus.publish(EntryStore.Event.UPDATE);
371 }
372 }
373 };
374 return new AddePrefConglomeration(Constants.PREF_LIST_ADDE_SERVERS, entryListener, entryPanel, entryToggles);
375 }
376
377 /**
378 * Enables or disables:<ul>
379 * <li>{@link JPanel} containing the {@link AddeEntry}s ({@link #cbPanel}).</li>
380 * <li>{@link JButton} that enables all available {@code AddeEntry}s ({@link #allOn}).</li>
381 * <li>{@code JButton} that disables all available {@code AddeEntry}s ({@link #allOff}).</li>
382 * </ul>
383 * Enabling the components allows the user to pick and choose servers, while
384 * disabling enables all servers.
385 *
386 * @param enabled {@code true} enables the components and {@code false} disables.
387 */
388 public void setGUIEnabled(final boolean enabled) {
389 if (cbPanel != null) {
390 GuiUtils.enableTree(cbPanel, enabled);
391 }
392 if (allOn != null) {
393 GuiUtils.enableTree(allOn, enabled);
394 }
395 if (allOff != null) {
396 GuiUtils.enableTree(allOff, enabled);
397 }
398 }
399
400 /**
401 * Sets the value of the {@link #PREF_LIST_SPECIFY} preference to
402 * {@code value}.
403 *
404 * @param entrySelection New value to associate with {@code PREF_LIST_SPECIFY}.
405 */
406 private void setSpecifyServers(final Selection entrySelection) {
407 entryStore.getIdvStore().put(PREF_LIST_SPECIFY, entrySelection.toString());
408 }
409
410 /**
411 * Returns the value of the {@link #PREF_LIST_SPECIFY} preference. Defaults
412 * to {@literal "ALL"}.
413 */
414 private Selection getSpecifyServers() {
415 String saved = entryStore.getIdvStore().get(PREF_LIST_SPECIFY, Selection.ALL_ENTRIES.toString());
416 Selection entrySelection;
417 if ("ALL".equalsIgnoreCase(saved)) {
418 entrySelection = Selection.ALL_ENTRIES;
419 } else if ("SPECIFY".equalsIgnoreCase(saved)) {
420 entrySelection = Selection.SPECIFIED_ENTRIES;
421 } else {
422 entrySelection = Selection.valueOf(saved);
423 }
424
425 return entrySelection;
426 }
427
428 /**
429 * This class is essentially a specialized tuple of the different things
430 * required by the {@link ucar.unidata.idv.IdvPreferenceManager}.
431 */
432 public static class AddePrefConglomeration {
433 private final String name;
434 private final PreferenceManager entryListener;
435 private final JPanel entryPanel;
436 private final Map<AddeEntry, JCheckBox> entryToggles;
437 public AddePrefConglomeration(String name, PreferenceManager entryListener, JPanel entryPanel, Map<AddeEntry, JCheckBox> entryToggles) {
438 this.name = name;
439 this.entryListener = entryListener;
440 this.entryPanel = entryPanel;
441 this.entryToggles = entryToggles;
442 }
443 public String getName() { return name; }
444 public PreferenceManager getEntryListener() { return entryListener; }
445 public JPanel getEntryPanel() { return entryPanel; }
446 public Map<AddeEntry, JCheckBox> getEntryToggles() { return entryToggles; }
447 }
448 }