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}