001/* 002 * $Id: AddePreferences.java,v 1.12 2011/03/24 16:06:34 davep Exp $ 003 * 004 * This file is part of McIDAS-V 005 * 006 * Copyright 2007-2011 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 */ 030package edu.wisc.ssec.mcidasv.servermanager; 031 032import static edu.wisc.ssec.mcidasv.util.CollectionHelpers.arrList; 033 034import java.awt.Dimension; 035import java.awt.Insets; 036import java.awt.Point; 037import java.awt.event.ActionEvent; 038import java.awt.event.ActionListener; 039import java.util.HashSet; 040import java.util.LinkedHashMap; 041import java.util.List; 042import java.util.Map; 043import java.util.Set; 044import java.util.Map.Entry; 045 046import javax.swing.Icon; 047import javax.swing.JButton; 048import javax.swing.JCheckBox; 049import javax.swing.JLabel; 050import javax.swing.JPanel; 051import javax.swing.JRadioButton; 052import javax.swing.JScrollPane; 053 054import org.bushe.swing.event.EventBus; 055import org.bushe.swing.event.annotation.AnnotationProcessor; 056import org.bushe.swing.event.annotation.EventTopicSubscriber; 057import org.slf4j.Logger; 058import org.slf4j.LoggerFactory; 059 060import ucar.unidata.idv.IdvObjectStore; 061import ucar.unidata.ui.CheckboxCategoryPanel; 062import ucar.unidata.util.GuiUtils; 063import ucar.unidata.xml.PreferenceManager; 064import ucar.unidata.xml.XmlObjectStore; 065 066import edu.wisc.ssec.mcidasv.Constants; 067import edu.wisc.ssec.mcidasv.McIDASV; 068import edu.wisc.ssec.mcidasv.McIdasPreferenceManager; 069import edu.wisc.ssec.mcidasv.servermanager.AddeEntry.EntryStatus; 070import edu.wisc.ssec.mcidasv.servermanager.AddeEntry.EntryType; 071import 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 */ 079public 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 } 377 }; 378 return new AddePrefConglomeration(Constants.PREF_LIST_ADDE_SERVERS, entryListener, entryPanel, entryToggles); 379 } 380 381 /** 382 * Enables or disables:<ul> 383 * <li>{@link JPanel} containing the {@link AddeEntry}s ({@link #cbPanel}).</li> 384 * <li>{@link JButton} that enables all available {@code AddeEntry}s ({@link #allOn}).</li> 385 * <li>{@code JButton} that disables all available {@code AddeEntry}s ({@link #allOff}).</li> 386 * </ul> 387 * Enabling the components allows the user to pick and choose servers, while 388 * disabling enables all servers. 389 * 390 * @param enabled {@code true} enables the components and {@code false} disables. 391 */ 392 public void setGUIEnabled(final boolean enabled) { 393 if (cbPanel != null) { 394 GuiUtils.enableTree(cbPanel, enabled); 395 } 396 if (allOn != null) { 397 GuiUtils.enableTree(allOn, enabled); 398 } 399 if (allOff != null) { 400 GuiUtils.enableTree(allOff, enabled); 401 } 402 } 403 404 /** 405 * Sets the value of the {@link #PREF_LIST_SPECIFY} preference to 406 * {@code value}. 407 * 408 * @param value New value to associate with {@code PREF_LIST_SPECIFY}. 409 */ 410 private void setSpecifyServers(final Selection entrySelection) { 411 entryStore.getIdvStore().put(PREF_LIST_SPECIFY, entrySelection.toString()); 412 } 413 414 /** 415 * Returns the value of the {@link #PREF_LIST_SPECIFY} preference. Defaults 416 * to {@literal "ALL"}. 417 */ 418 private Selection getSpecifyServers() { 419 String saved = entryStore.getIdvStore().get(PREF_LIST_SPECIFY, Selection.ALL_ENTRIES.toString()); 420 Selection entrySelection; 421 if ("ALL".equalsIgnoreCase(saved)) { 422 entrySelection = Selection.ALL_ENTRIES; 423 } else if ("SPECIFY".equalsIgnoreCase(saved)) { 424 entrySelection = Selection.SPECIFIED_ENTRIES; 425 } else { 426 entrySelection = Selection.valueOf(saved); 427 } 428 429 return entrySelection; 430 } 431 432 /** 433 * This class is essentially a specialized tuple of the different things 434 * required by the {@link ucar.unidata.idv.IdvPreferenceManager}. 435 */ 436 public static class AddePrefConglomeration { 437 private final String name; 438 private final PreferenceManager entryListener; 439 private final JPanel entryPanel; 440 private final Map<AddeEntry, JCheckBox> entryToggles; 441 public AddePrefConglomeration(String name, PreferenceManager entryListener, JPanel entryPanel, Map<AddeEntry, JCheckBox> entryToggles) { 442 this.name = name; 443 this.entryListener = entryListener; 444 this.entryPanel = entryPanel; 445 this.entryToggles = entryToggles; 446 } 447 public String getName() { return name; } 448 public PreferenceManager getEntryListener() { return entryListener; } 449 public JPanel getEntryPanel() { return entryPanel; } 450 public Map<AddeEntry, JCheckBox> getEntryToggles() { return entryToggles; } 451 } 452}