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