001package jmri.jmrix.can.cbus.swing; 002 003import java.awt.Dimension; 004import java.awt.GridLayout; 005import java.util.EnumSet; 006import java.util.HashMap; 007import javax.annotation.Nonnull; 008import javax.swing.BoxLayout; 009import javax.swing.JScrollPane; 010import javax.swing.JPanel; 011import javax.swing.WindowConstants; 012import jmri.jmrix.can.cbus.CbusFilter; 013import jmri.jmrix.can.cbus.CbusFilterType; 014import jmri.jmrix.can.cbus.swing.configtool.ConfigToolPane; 015import jmri.jmrix.can.cbus.swing.console.CbusConsolePane; 016import jmri.util.JmriJFrame; 017import jmri.util.ThreadingUtil; 018 019// import org.slf4j.Logger; 020// import org.slf4j.LoggerFactory; 021 022/** 023 * Frame to control an instance of CBUS filter to filter events. 024 * Currently used in CBUS Console + Event capture tool 025 * 026 * @author Steve Young Copyright (C) 2018, 2020 027 */ 028public class CbusFilterFrame extends JmriJFrame { 029 030 private final CbusConsolePane _console; 031 private final ConfigToolPane _evCap; 032 private final CbusFilter _filter; 033 private final HashMap<Integer, CbusFilterPanel> hash_map; 034 035 private final static Dimension minimumSize = new Dimension(400, 200); 036 037 /** 038 * Create a new instance of CbusFilterFrame. 039 * @param console CbusConsolePane Instance to Filter 040 * @param evCap Event Capture Tool Instance to Filter 041 */ 042 public CbusFilterFrame(CbusConsolePane console, ConfigToolPane evCap) { 043 super(); 044 _console = console; 045 _evCap = evCap; 046 _filter = new CbusFilter(this); 047 hash_map = new HashMap<>(CbusFilter.getHMapSize(CbusFilter.CFMAXCATS + CbusFilter.CFMAX_NODES)); 048 super.setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE); 049 } 050 051 /** 052 * Pass text to a CbusConsole instance. 053 * @param text to include in the Console Log.2 054 */ 055 protected void updateListeners(String text){ 056 if ( _console != null) { 057 _console.nextLine( text + " \n", text + " \n", -1); 058 } 059 } 060 061 /** 062 * Get Filter Title. 063 * @return Title incorporating CbusConsole or Event Capture Instance. 064 */ 065 @Nonnull 066 protected String title() { 067 if (_console != null) { 068 return _console.getTitle() + " " + Bundle.getMessage("EventFilterTitleX", ""); 069 } else if (_evCap != null) { 070 return _evCap.getTitle() + " " + Bundle.getMessage("EventFilterTitleX", ""); 071 } 072 return Bundle.getMessage("EventFilterTitleX", ""); 073 } 074 075 /** 076 * {@inheritDoc} 077 */ 078 @Override 079 public void initComponents() { 080 081 getContentPane().setLayout(new GridLayout(1,0)); 082 setTitle(title()); 083 084 JPanel fPane = new JPanel(); 085 fPane.setLayout(new BoxLayout(fPane, BoxLayout.Y_AXIS)); 086 087 EnumSet.range(CbusFilterType.CFIN, CbusFilterType.CFNODEMAX).forEach((singleFilter) -> { 088 addPaneToMap(fPane, new CbusFilterPanel(this,singleFilter)); 089 }); 090 091 for ( int j=0 ; ( j < CbusFilter.CFMAX_NODES ) ; j++ ){ 092 addPaneToMap(fPane, new CbusFilterPanel(this,(CbusFilter.CFMAXCATS + j))); 093 } 094 095 EnumSet.range(CbusFilterType.CFDATA, CbusFilterType.CFUNKNOWN).forEach((singleFilter) -> { 096 addPaneToMap(fPane, new CbusFilterPanel(this,singleFilter)); 097 }); 098 099 JScrollPane fPaneScroll = new JScrollPane(); 100 fPaneScroll.setPreferredSize(minimumSize); 101 fPaneScroll.getViewport().add(fPane); 102 103 fPaneScroll.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); 104 fPaneScroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); 105 fPaneScroll.setVisible(true); 106 getContentPane().add(fPaneScroll); 107 // prevent button areas from expanding 108 pack(); 109 updateListeners(Bundle.getMessage("FilterWindowActive")); 110 } 111 112 /** 113 * Add Pane. 114 * @param fPane main Pane to add to 115 * @param panel CbusFilterPanel to add 116 */ 117 public void addPaneToMap(JPanel fPane, CbusFilterPanel panel) { 118 fPane.add(panel); 119 hash_map.put(panel.getIndex(),panel); 120 } 121 122 /** 123 * Add Node to empty node Panel. 124 * 125 * @param nodenum Node Number 126 * @param position Position in main Filter list 127 */ 128 public void addNode(int nodenum, int position) { 129 hash_map.get(position).setNode(nodenum, 130 hash_map.get(CbusFilterType.CFNODE.ordinal()).getButton(), 131 hash_map.get(CbusFilterType.CFNODEMIN.ordinal()).getVisible() 132 ); 133 } 134 135 /** 136 * Change which categories are filtered. 137 * 138 * @param id Filter ID, includes the Node filters. 139 * @param newselected If the category is now visible 140 * @param changedFilter ENUM value of Category 141 */ 142 protected void checkBoxChanged ( int id, boolean newselected, @Nonnull CbusFilterType changedFilter){ 143 _filter.setFilter( id, newselected); // update main filter boolean 144 if (changedFilter.alwaysDisplay()) { 145 setCategoryChanged(id,newselected,changedFilter); 146 } 147 else { 148 if (changedFilter.getCategory()==null) { 149 return; 150 } 151 boolean hasTrue = hash_map.values().stream().filter((value) -> ( ( 152 value.getFilterType().getCategory() == changedFilter.getCategory() ) 153 && ( value.getAvailable() ) 154 && (value.getButton()))).count()>0; 155 156 boolean hasFalse = hash_map.values().stream().filter((value) -> ( ( 157 value.getFilterType().getCategory() == changedFilter.getCategory() ) 158 && ( value.getAvailable() ) 159 && (!value.getButton()))).count()>0; 160 161 setFilterMaybeMixed(changedFilter.getCategory(),hasTrue,hasFalse); 162 163 } 164 } 165 166 /** 167 * Set Filter Category Pass / Filter / Mixed status 168 * 169 * @param changedFilter Filter Category 170 * @param hasTrue has Passes in children 171 * @param hasFalse has Filters in children 172 */ 173 private void setFilterMaybeMixed( CbusFilterType changedFilter, boolean hasTrue, boolean hasFalse) { 174 if ( hasTrue && hasFalse ) { 175 hash_map.get(changedFilter.ordinal()).setMixed(); 176 _filter.setFilter( changedFilter.ordinal(), false); 177 } 178 else if ( hasTrue && !hasFalse ) { 179 hash_map.get(changedFilter.ordinal()).setPass(true); 180 _filter.setFilter( changedFilter.ordinal(), true); 181 } 182 else if ( !hasTrue && hasFalse ) { 183 hash_map.get(changedFilter.ordinal()).setPass(false); 184 _filter.setFilter( changedFilter.ordinal(), false); 185 } 186 } 187 188 /** 189 * Category master button, change status of all children. 190 * 191 * @param id Filter ID, includes the Node filters. 192 * @param newselected If the category is now visible 193 * @param changedFilter ENUM value of Category 194 */ 195 private void setCategoryChanged( int id, boolean newselected, @Nonnull CbusFilterType changedFilter){ 196 hash_map.values().stream().filter((value) -> ( ( 197 value.getFilterType().getCategory() == changedFilter ) 198 && ( value.getAvailable() ) )).forEach((value) -> { 199 _filter.setFilter((id), newselected); // set main boolean filter 200 value.setPass(newselected); 201 }); 202 } 203 204 /** 205 * Change which categories are displayed following button click. 206 * 207 * @param id Filter ID, includes the Node filters. 208 * @param newselected If the category is now visible 209 * @param categoryType ENUM value of Category 210 */ 211 protected void showFiltersChanged(int id, boolean newselected, @Nonnull CbusFilterType categoryType){ 212 hash_map.values().stream().filter((value) -> ( ( 213 value.getFilterType().getCategory() == categoryType ) 214 && ( value.getAvailable() ) )).forEach((value) -> { 215 value.visibleFilter(newselected); 216 }); 217 } 218 219 /** 220 * Increment a filter panel pass value 221 * @param id Panel ID 222 */ 223 public void passIncrement(int id){ 224 ThreadingUtil.runOnGUIEventually( ()->{ 225 hash_map.get(id).incrementPass(); 226 }); 227 } 228 229 /** 230 * Set the event or node min and max values. 231 * 232 * @param filter CFEVENTMIN, CFEVENTMAX, CFNODEMIN or CFNODEMAX 233 * @param value min or max value 234 */ 235 protected void setMinMax(@Nonnull CbusFilterType filter, int value){ 236 _filter.setMinMax(filter,value); 237 switch (filter) { 238 case CFEVENTMIN: 239 updateListeners(Bundle.getMessage("MinEventSet",value)); 240 break; 241 case CFEVENTMAX: 242 updateListeners(Bundle.getMessage("MaxEventSet",value)); 243 break; 244 case CFNODEMIN: 245 updateListeners(Bundle.getMessage("MinNodeSet",value)); 246 break; 247 case CFNODEMAX: 248 updateListeners(Bundle.getMessage("MaxNodeSet",value)); 249 break; 250 default: 251 break; 252 } 253 } 254 255 /** 256 * Filter a CanReply or CanMessage. 257 * 258 * @param m CanMessage or CanReply 259 * @return true when to apply filter, false to not filter. 260 * 261 */ 262 public boolean filter(@Nonnull jmri.jmrix.AbstractMessage m) { 263 int result = _filter.filter(m); 264 if ( result > -1 ) { 265 ThreadingUtil.runOnGUIEventually( ()->{ 266 hash_map.get(result).incrementFilter(); 267 }); 268 return true; 269 } 270 return false; 271 } 272 273 // private final static Logger log = LoggerFactory.getLogger(CbusFilterFrame.class); 274}