/*
* Copyright (c) 2012 Sam Harwell, Tunnel Vision Laboratories LLC
* All rights reserved.
*
* The source code of this document is proprietary work, and is not licensed for
* distribution. For information about licensing, contact Sam Harwell at:
* sam@tunnelvisionlabs.com
*/
package org.antlr.netbeans.editor.navigation;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.prefs.Preferences;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;
import javax.swing.event.ChangeEvent;
import org.openide.util.NbPreferences;
/** Handles creation and manipulation with boolean state filters.
*
* @author Dafe Simomek
*/
public final class FiltersManager {
private final FiltersComponent comp;
static FiltersManager create(FiltersDescription descr) {
return new FiltersManager(descr);
}
/** Returns true when given filter is selected, false otherwise.
* Note that this method is thread safe, can be called from any thread
* (and usually will, as clients will call this from loadContent naturally)
* @param filterName
* @return
*/
public boolean isSelected(String filterName) {
return comp.isSelected(filterName);
}
/** Sets boolean value of filter with given name. True means filter is
* selected (enabled), false otherwise. Note, must be called from AWT thread.
* @param filterName
* @param value
*/
public void setSelected(String filterName, boolean value) {
comp.setFilterSelected(filterName, value);
}
/**
* @param sortButtons
* @return component instance visually representing filters
*/
public JComponent getComponent(JToggleButton[] sortButtons) {
comp.addSortButtons(sortButtons);
return comp;
}
/** @return Filters description */
public FiltersDescription getDescription() {
return comp.getDescription();
}
/** Assigns listener for listening to filter changes
* @param l
*/
public void hookChangeListener(FilterChangeListener l) {
comp.hookFilterChangeListener(l);
}
/** Interface for listening to changes of filter states contained in FIltersPanel
*/
public interface FilterChangeListener {
/** Called whenever some changes in state of filters contained in
* filters panel is triggered
* @param e
*/
public void filterStateChanged(ChangeEvent e);
} // end of FilterChangeListener
/** Private, creation managed by factory method 'create' */
private FiltersManager(FiltersDescription descr) {
comp = new FiltersComponent(descr);
}
/** Swing component representing filters in panel filled with toggle buttons.
* Provides thread safe access to the states of filters by copying states
* into private map, properly sync'ed.
*/
private class FiltersComponent extends Box implements ActionListener {
/** list of <JToggleButton> visually representing filters */
private List<JToggleButton> toggles;
/** description of filters */
private final FiltersDescription filtersDesc;
/** lock for listener */
private final Object L_LOCK = new Object();
/** listener */
private FilterChangeListener clientL;
/** lock for map of filter states */
private final Object STATES_LOCK = new Object();
/** copy of filter states for accessing outside AWT */
private Map<String, Boolean> filterStates;
private JToolBar toolbar;
/** Returns selected state of given filter, thread safe.
*/
public boolean isSelected(String filterName) {
Boolean result;
synchronized (STATES_LOCK) {
if (filterStates == null) {
// Swing toggles not initialized yet
int index = filterIndexForName(filterName);
if (index < 0) {
return false;
} else {
return filtersDesc.isSelected(index);
}
}
result = filterStates.get(filterName);
}
if (result == null) {
throw new IllegalArgumentException("Filter " + filterName + " not found.");
}
return result.booleanValue();
}
/** Sets filter value, AWT only */
public void setFilterSelected(String filterName, boolean value) {
assert SwingUtilities.isEventDispatchThread();
int index = filterIndexForName(filterName);
if (index < 0) {
throw new IllegalArgumentException("Filter " + filterName + " not found.");
}
// update both swing control and states map
toggles.get(index).setSelected(value);
synchronized (STATES_LOCK) {
filterStates.put(filterName, Boolean.valueOf(value));
}
// notify
fireChange();
}
public void hookFilterChangeListener(FilterChangeListener l) {
synchronized (L_LOCK) {
clientL = l;
}
}
public FiltersDescription getDescription() {
return filtersDesc;
}
/** Not public, instances created using factory method createPanel */
FiltersComponent(FiltersDescription descr) {
super(BoxLayout.X_AXIS);
this.filtersDesc = descr;
// always create swing content in AWT thread
if (!SwingUtilities.isEventDispatchThread()) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
initPanel();
}
});
} else {
initPanel();
}
}
/** Called only from AWT */
private void initPanel() {
setBorder(new EmptyBorder(1, 2, 3, 5));
// configure toolbar
toolbar = new NoBorderToolBar(JToolBar.HORIZONTAL);
toolbar.setFloatable(false);
toolbar.setRollover(true);
toolbar.setBorderPainted(false);
toolbar.setBorder(BorderFactory.createEmptyBorder());
toolbar.setOpaque(false);
toolbar.setFocusable(false);
// create toggle buttons
int filterCount = filtersDesc.getFilterCount();
toggles = new ArrayList<>(filterCount);
Map<String, Boolean> fStates = new HashMap<>(filterCount * 2);
for (int i = 0; i < filterCount; i++) {
JToggleButton toggleButton = createToggle(fStates, i);
toggles.add(toggleButton);
}
// add toggle buttons
JToggleButton curToggle;
for (int i = 0; i < toggles.size(); i++) {
curToggle = toggles.get(i);
curToggle.addActionListener(this);
toolbar.add(curToggle);
if (i != toggles.size() - 1) {
toolbar.add(new Space());
}
}
add(toolbar);
// initialize member states map
synchronized (STATES_LOCK) {
filterStates = fStates;
}
}
private void addSortButtons(JToggleButton[] sortButtons) {
Dimension space = new Dimension(3, 0);
for (JToggleButton button : sortButtons) {
Icon icon = button.getIcon();
Dimension size = new Dimension(icon.getIconWidth() + 6, icon.getIconHeight() + 4);
button.setPreferredSize(size);
button.setMargin(new Insets(2, 3, 2, 3));
toolbar.addSeparator(space);
toolbar.add(button);
}
}
private Preferences getPreferences() {
return NbPreferences.forModule(FiltersManager.class);
}
private JToggleButton createToggle(Map<String, Boolean> fStates, int index) {
boolean isSelected = getPreferences().getBoolean(filtersDesc.getName(index), filtersDesc.isSelected(index));
Icon icon = filtersDesc.getSelectedIcon(index);
// ensure small size, just for the icon
JToggleButton result = new JToggleButton(icon, isSelected);
Dimension size = new Dimension(icon.getIconWidth() + 6, icon.getIconHeight() + 4);
result.setPreferredSize(size);
result.setMargin(new Insets(2, 3, 2, 3));
result.setToolTipText(filtersDesc.getTooltip(index));
result.setFocusable(false);
fStates.put(filtersDesc.getName(index), Boolean.valueOf(isSelected));
return result;
}
/** Finds and returns index of filter with given name or -1 if no
* such filter exists.
*/
private int filterIndexForName(String filterName) {
int filterCount = filtersDesc.getFilterCount();
String curName;
for (int i = 0; i < filterCount; i++) {
curName = filtersDesc.getName(i);
if (filterName.equals(curName)) {
return i;
}
}
return -1;
}
/** Reactions to toggle button click, */
@Override
public void actionPerformed(ActionEvent e) {
// copy changed state first
JToggleButton toggle = (JToggleButton) e.getSource();
int index = toggles.indexOf(e.getSource());
synchronized (STATES_LOCK) {
filterStates.put(filtersDesc.getName(index),
Boolean.valueOf(toggle.isSelected()));
}
// notify
fireChange();
}
private void fireChange() {
FilterChangeListener lCopy;
synchronized (L_LOCK) {
// no listener = no notification
if (clientL == null) {
return;
}
lCopy = clientL;
}
// notify listener
lCopy.filterStateChanged(new ChangeEvent(FiltersManager.this));
}
@Override
public void removeNotify() {
//remember filter settings
if (null != filterStates) {
Preferences prefs = getPreferences();
for (String filterName : filterStates.keySet()) {
prefs.putBoolean(filterName, filterStates.get(filterName));
}
}
super.removeNotify();
}
} // end of FiltersComponent
private static class Space extends JComponent {
private static final Dimension SPACE = new Dimension(3, 3);
@Override
public Dimension getPreferredSize() {
return getMinimumSize();
}
@Override
public Dimension getMaximumSize() {
return getMinimumSize();
}
@Override
public Dimension getMinimumSize() {
return SPACE;
}
}
}