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