001package jmri.jmrix.can.cbus.swing;
002
003import java.awt.Color;
004import java.awt.GridLayout;
005import javax.annotation.Nonnull;
006import javax.swing.*;
007import javax.swing.event.ChangeEvent;
008import javax.swing.plaf.basic.BasicToggleButtonUI;
009import javax.swing.text.DefaultFormatter;
010import jmri.jmrit.catalog.NamedIcon;
011import jmri.jmrix.can.cbus.CbusFilterType;
012import jmri.util.ThreadingUtil;
013
014// import org.slf4j.Logger;
015// import org.slf4j.LoggerFactory;
016
017/**
018 * Creates Panel for displaying a single filter
019 * @author Steve Young Copyright (C) 2018, 2020
020 */
021public class CbusFilterPanel extends JPanel {
022    
023    private final CbusFilterType _fType;
024    private final int _index;
025    private final CbusFilterFrame _filterFrame;
026    private String _textLabel;
027    private int _countFilter=0;
028    private int _countPass=0;
029    private JLabel fLabel;
030    private JPanel evPane;
031    private boolean _available;
032    private String _buttonText = Bundle.getMessage("ButtonPass");
033    
034    private final static Color greenish = new Color(110, 235, 131);
035    private final static Color redish = new Color(255, 132, 84);
036    private final static Color amberish = new Color(228, 255, 26);
037
038    // Buttons to enable/disable filters
039    private JToggleButton enableButton;
040    private JToggleButton catButton;
041
042    /**
043     * Create a new CbusFilterPanel for filtering any Nodes which are heard
044     * This is created at Frame startup, though #initComponents
045     * is not called until the Node is actually set by #setNode
046     * 
047     * @param filterFrame parent Frame
048     * @param index Filter Index
049     */
050    public CbusFilterPanel(CbusFilterFrame filterFrame, int index) {
051        super();
052        _index = index;
053        _filterFrame = filterFrame;
054        _fType = CbusFilterType.CFNODES;
055        _textLabel = Bundle.getMessage("CbusNodes");
056        _available = false;
057        setVisible(false);
058    }
059    
060    /**
061     * Create a new CbusFilterPanel for filtering any CbusFilterType.
062     * This is created at Frame startup, #initComponents
063     * is called straight away.
064     * 
065     * @param filterFrame parent Frame
066     * @param fType Filter to display
067     */
068    public CbusFilterPanel(CbusFilterFrame filterFrame, CbusFilterType fType){
069        super();
070         _index = fType.ordinal();
071        _filterFrame = filterFrame;
072        _fType = fType;
073        _textLabel = _fType.getName();
074        _available=true;
075        initComponents();
076    }
077    
078    private static double _iconScale = 0.25;
079    
080    private NamedIcon getCollapsed() {
081        NamedIcon collapsed = new NamedIcon("resources/icons/decorations/ArrowStyle2.png", "resources/icons/decorations/ArrowStyle2.png");
082        collapsed.scale(_iconScale, this);
083        return collapsed;
084    }
085    
086    private NamedIcon getShowing() {
087        NamedIcon showing = new NamedIcon("resources/icons/decorations/ArrowStyle2.png", "resources/icons/decorations/ArrowStyle2.png");
088        showing.scale(_iconScale,this);
089        showing.setRotation(3, this);
090        return showing;
091    }
092
093    /**
094     * Initialise the Pane.
095     */
096    protected final void initComponents() {
097        
098        this.setLayout(new GridLayout(1,1,30,30)); // row, col, hgap, vgap
099
100        // Pane to hold Event
101        evPane = new JPanel();
102        evPane.setBackground(Color.white);
103        
104        catButton = new JToggleButton();
105        fLabel = new JLabel(_textLabel,SwingConstants.RIGHT);
106        fLabel.setToolTipText(getFilterType().getToolTip());
107        if (getFilterType().isCategoryHead()){
108            evPane.setLayout(new GridLayout(1,2,0,10));
109            configCatButton(catButton);
110            evPane.add(catButton);
111        }
112        else {
113            evPane.setLayout(new GridLayout(1,2,40,10));
114        
115            if ( _fType.showSpinners() ) {
116                evPane.add(getNewSpinnerAndButton(_fType));
117            } else {
118                evPane.add(fLabel);
119            }
120        }
121        enableButton = getNewEnableButton();
122        
123        evPane.add(enableButton);
124        evPane.setVisible(true);
125        this.add(evPane);
126        this.setVisible( getFilterType().alwaysDisplay() );
127    }
128    
129    private void configCatButton(@Nonnull JToggleButton button){
130    
131        button.setBackground(Color.white);
132        button.setText(_textLabel);
133        button.setIcon(getCollapsed());
134        button.setHorizontalAlignment(SwingConstants.RIGHT);
135        button.setHorizontalTextPosition(JLabel.LEFT);
136        button.setFocusPainted(false);
137        button.setBorderPainted(false);
138        button.addActionListener((java.awt.event.ActionEvent e) -> {
139            if (button.isSelected()) {
140                // log.warn("selected category index {}",_index);
141                button.setIcon(getShowing());
142            } else {
143                // log.warn("deselected category index {} ",_index);
144                button.setIcon(getCollapsed());
145            }
146            ThreadingUtil.runOnGUIEventually( ()->{
147                _filterFrame.showFiltersChanged(_index,button.isSelected(),getFilterType());
148            });
149        });
150    }
151    
152    private JPanel getNewSpinnerAndButton(@Nonnull CbusFilterType fType){
153    
154        JPanel spinnerAndButton = new JPanel();
155        spinnerAndButton.setLayout(new GridLayout(1,2,0,0));
156        JSpinner spinner = new JSpinner(new SpinnerNumberModel(0, 0, 65535, 1));
157        
158        JSpinner.NumberEditor editor = new JSpinner.NumberEditor(spinner, "#");
159        spinner.setEditor(editor);
160        
161        JFormattedTextField field = (JFormattedTextField) editor.getComponent(0);
162        DefaultFormatter formatter = (DefaultFormatter) field.getFormatter();
163        formatter.setCommitsOnValidEdit(true);
164        spinner.addChangeListener((ChangeEvent e) -> {
165            _filterFrame.setMinMax(fType,(Integer) spinner.getValue() );
166        });
167
168        spinnerAndButton.setBackground(Color.white);
169        spinnerAndButton.add(spinner);
170        spinnerAndButton.add(fLabel);
171    
172        return spinnerAndButton;
173    }
174    
175    private JToggleButton getNewEnableButton(){
176        JToggleButton newButton = new JToggleButton();
177        newButton.setUI(new BasicToggleButtonUI()); //Removes selectColor
178        newButton.setText( newTextString() );
179        newButton.setBackground(greenish);
180        newButton.setFocusPainted(false);
181        newButton.setToolTipText(Bundle.getMessage("ButtonPassTip"));
182
183        // connect actions to buttons
184        newButton.addActionListener((java.awt.event.ActionEvent e) -> {
185            resetenableButton();
186            ThreadingUtil.runOnGUIEventually( ()->{
187                _filterFrame.checkBoxChanged(_index,enableButton.isSelected(),getFilterType());
188                StringBuilder txt = new StringBuilder();
189                txt.append(_textLabel)
190                .append(": ")
191                .append(_buttonText);
192                if (getFilterType().isCategoryHead()){
193                    txt.append(" ")
194                    .append(Bundle.getMessage("All"));
195                }
196                _filterFrame.updateListeners(txt.toString());
197            });
198        });
199        return newButton;
200    }
201    
202    /**
203     * Reset the Filter Button.
204     */
205    private void resetenableButton(){
206        if (enableButton.isSelected()) {
207            _buttonText = Bundle.getMessage("ButtonFilter");
208            enableButton.setText( newTextString() );
209            enableButton.setToolTipText(Bundle.getMessage("ButtonFilterTip"));
210            enableButton.setBackground(redish);
211        } else {
212            _buttonText = Bundle.getMessage("ButtonPass");
213            enableButton.setText( newTextString() );
214            enableButton.setToolTipText(Bundle.getMessage("ButtonPassTip"));
215            enableButton.setBackground(greenish);
216        }
217    }
218    
219    /**
220     * If panel is displaying a parent category, some children categories may
221     * be both Active and Inactive.
222     */
223    protected void setMixed() {
224        _buttonText = Bundle.getMessage("ButtonMixed");
225        enableButton.setText( newTextString() );
226        enableButton.setToolTipText(Bundle.getMessage("ButtonMixedTip"));
227        enableButton.setBackground(amberish);
228        enableButton.setSelected(false);
229    }
230    
231    /**
232     * Get the Filter Index, value for main Filter.
233     * @return Filter Index.
234     */
235    protected final int getIndex() {
236        return _index;
237    }
238    
239    /**
240     * Show or Hide the Filter Panel
241     * @param showornot true to show, false to hide.
242     */
243    protected void visibleFilter(boolean showornot) {
244        this.setVisible(showornot);
245    }
246    
247    /**
248     * Get the Filter Type in use by the panel.
249     * @return Filter Type ENUM
250     */
251    @Nonnull
252    protected final CbusFilterType getFilterType(){
253        return _fType;
254    }
255
256    /**
257     * Increment the number of Filtered Frames.
258     * Updates text label with counts.
259     */
260    protected void incrementFilter() {
261        _countFilter++;
262        enableButton.setText(newTextString());
263    }
264    
265    /**
266     * Increment the number of allowed Frames.
267     * Updates text label with counts.
268     */
269    protected void incrementPass() {
270        _countPass++;
271        enableButton.setText(newTextString());
272    }
273    
274    /**
275     * Get the button text string, with text label and filter / pass counts.
276     * @return Full text string
277     */
278    @Nonnull
279    private String newTextString() {
280        StringBuilder t = new StringBuilder();
281        t.append(_buttonText)
282        .append(" ( ")
283        .append(_countPass)
284        .append(" / ")
285        .append(_countFilter)
286        .append(" ) ");
287        return t.toString();
288    }
289    
290    /**
291     * Get if the button is currently set to filter.
292     * @return true if filtering, else false.
293     */
294    protected boolean getButton() {
295        return enableButton.isSelected();
296    }
297    
298    /**
299     * Get if the Panel is visible
300     * @return true if visible, else false.
301     */
302    protected boolean getVisible() {
303        return this.isVisible();
304    }
305
306    /**
307     * Get if the Panel is in use.
308     * @return true if in use by a Filter / Node, else false.
309     */
310    protected boolean getAvailable() {
311        return _available;
312    }
313    
314    /**
315     * Set the Panel for a Node.
316     * @param node Node Number
317     * @param filter true to Start filter active, false to pass.
318     * @param show true to display, false to hide.
319     */
320    protected void setNode( int node, boolean filter, boolean show ) {
321        initComponents();
322        _available=true;
323        _textLabel=Bundle.getMessage("CbusNode") + Integer.toString(node);
324        fLabel.setText(_textLabel);
325        this.setVisible(show);
326        setPass(filter);
327    }
328    
329    /**
330     * Set the panel button to display Pass / Filter.
331     * @param trueorfalse true to pass, false to filter.
332     */
333    protected void setPass(boolean trueorfalse) {
334        enableButton.setSelected(trueorfalse);
335        resetenableButton();
336    }
337    
338    // private final static Logger log = LoggerFactory.getLogger(CbusFilterPanel.class);
339}