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