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 }