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 }