001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.modules.java.navigation.base;
043:
044: import java.awt.Dimension;
045: import java.awt.Insets;
046: import java.awt.event.ActionEvent;
047: import java.awt.event.ActionListener;
048: import java.util.ArrayList;
049: import java.util.HashMap;
050: import java.util.List;
051: import java.util.Map;
052: import java.util.prefs.Preferences;
053: import javax.swing.BorderFactory;
054: import javax.swing.Box;
055: import javax.swing.BoxLayout;
056: import javax.swing.ButtonGroup;
057: import javax.swing.Icon;
058: import javax.swing.JComponent;
059: import javax.swing.JToggleButton;
060: import javax.swing.JToolBar;
061: import javax.swing.SwingUtilities;
062: import javax.swing.border.EmptyBorder;
063: import javax.swing.event.ChangeEvent;
064: import org.netbeans.modules.java.navigation.NoBorderToolBar;
065: import org.openide.util.NbPreferences;
066:
067: /** Handles creation and manipulation with boolean state filters.
068: *
069: * @author Dafe Simomek
070: */
071: public final class FiltersManager {
072:
073: private FiltersComponent comp;
074:
075: static FiltersManager create(FiltersDescription descr) {
076: return new FiltersManager(descr);
077: }
078:
079: /** Returns true when given filter is selected, false otherwise.
080: * Note that this method is thread safe, can be called from any thread
081: * (and usually will, as clients will call this from loadContent naturally)
082: */
083: public boolean isSelected(String filterName) {
084: return comp.isSelected(filterName);
085: }
086:
087: /** Sets boolean value of filter with given name. True means filter is
088: * selected (enabled), false otherwise. Note, must be called from AWT thread.
089: */
090: public void setSelected(String filterName, boolean value) {
091: comp.setFilterSelected(filterName, value);
092: }
093:
094: /** @return component instance visually representing filters */
095: public JComponent getComponent(JToggleButton[] sortButtons) {
096: comp.addSortButtons(sortButtons);
097: return comp;
098: }
099:
100: /** @return Filters description */
101: public FiltersDescription getDescription() {
102: return comp.getDescription();
103: }
104:
105: /** Assigns listener for listening to filter changes */
106: public void hookChangeListener(FilterChangeListener l) {
107: comp.hookFilterChangeListener(l);
108: }
109:
110: /** Interface for listening to changes of filter states contained in FIltersPanel
111: */
112: public interface FilterChangeListener {
113: /** Called whenever some changes in state of filters contained in
114: * filters panel is triggered
115: */
116: public void filterStateChanged(ChangeEvent e);
117:
118: } // end of FilterChangeListener
119:
120: /** Private, creation managed by factory method 'create' */
121: private FiltersManager(FiltersDescription descr) {
122: comp = new FiltersComponent(descr);
123: }
124:
125: /** Swing component representing filters in panel filled with toggle buttons.
126: * Provides thread safe access to the states of filters by copying states
127: * into private map, properly sync'ed.
128: */
129: private class FiltersComponent extends Box implements
130: ActionListener {
131:
132: /** list of <JToggleButton> visually representing filters */
133: private List<JToggleButton> toggles;
134: /** description of filters */
135: private final FiltersDescription filtersDesc;
136:
137: /** lock for listener */
138: private Object L_LOCK = new Object();
139: /** listener */
140: private FilterChangeListener clientL;
141:
142: /** lock for map of filter states */
143: private Object STATES_LOCK = new Object();
144: /** copy of filter states for accessing outside AWT */
145: private Map<String, Boolean> filterStates;
146:
147: private JToolBar toolbar;
148:
149: /** Returns selected state of given filter, thread safe.
150: */
151: public boolean isSelected(String filterName) {
152: Boolean result;
153: synchronized (STATES_LOCK) {
154: if (filterStates == null) {
155: // Swing toggles not initialized yet
156: int index = filterIndexForName(filterName);
157: if (index < 0) {
158: return false;
159: } else {
160: return filtersDesc.isSelected(index);
161: }
162: }
163: result = filterStates.get(filterName);
164: }
165:
166: if (result == null) {
167: throw new IllegalArgumentException("Filter "
168: + filterName + " not found.");
169: }
170: return result.booleanValue();
171: }
172:
173: /** Sets filter value, AWT only */
174: public void setFilterSelected(String filterName, boolean value) {
175: assert SwingUtilities.isEventDispatchThread();
176:
177: int index = filterIndexForName(filterName);
178: if (index < 0) {
179: throw new IllegalArgumentException("Filter "
180: + filterName + " not found.");
181: }
182: // update both swing control and states map
183: toggles.get(index).setSelected(value);
184: synchronized (STATES_LOCK) {
185: filterStates.put(filterName, Boolean.valueOf(value));
186: }
187: // notify
188: fireChange();
189: }
190:
191: public void hookFilterChangeListener(FilterChangeListener l) {
192: synchronized (L_LOCK) {
193: clientL = l;
194: }
195: }
196:
197: public FiltersDescription getDescription() {
198: return filtersDesc;
199: }
200:
201: /** Not public, instances created using factory method createPanel */
202: FiltersComponent(FiltersDescription descr) {
203: super (BoxLayout.X_AXIS);
204: this .filtersDesc = descr;
205: // always create swing content in AWT thread
206: if (!SwingUtilities.isEventDispatchThread()) {
207: SwingUtilities.invokeLater(new Runnable() {
208: public void run() {
209: initPanel();
210: }
211: });
212: } else {
213: initPanel();
214: }
215: }
216:
217: /** Called only from AWT */
218: private void initPanel() {
219: setBorder(new EmptyBorder(1, 2, 3, 5));
220:
221: // configure toolbar
222: toolbar = new NoBorderToolBar(JToolBar.HORIZONTAL);
223: toolbar.setFloatable(false);
224: toolbar.setRollover(true);
225: toolbar.setBorderPainted(false);
226: toolbar.setBorder(BorderFactory.createEmptyBorder());
227: toolbar.setOpaque(false);
228: toolbar.setFocusable(false);
229: // create toggle buttons
230: int filterCount = filtersDesc.getFilterCount();
231: toggles = new ArrayList<JToggleButton>(filterCount);
232: JToggleButton toggleButton = null;
233:
234: Map<String, Boolean> fStates = new HashMap<String, Boolean>(
235: filterCount * 2);
236:
237: for (int i = 0; i < filterCount; i++) {
238: toggleButton = createToggle(fStates, i);
239: toggles.add(toggleButton);
240: }
241:
242: // add toggle buttons
243: JToggleButton curToggle;
244: Dimension space = new Dimension(3, 0);
245: for (int i = 0; i < toggles.size(); i++) {
246: curToggle = toggles.get(i);
247: curToggle.addActionListener(this );
248: toolbar.add(curToggle);
249: if (i != toggles.size() - 1) {
250: toolbar.addSeparator(space);
251: }
252: }
253:
254: add(toolbar);
255:
256: // initialize member states map
257: synchronized (STATES_LOCK) {
258: filterStates = fStates;
259: }
260: }
261:
262: private void addSortButtons(JToggleButton[] sortButtons) {
263: Dimension space = new Dimension(3, 0);
264: for (JToggleButton button : sortButtons) {
265: Icon icon = button.getIcon();
266: Dimension size = new Dimension(icon.getIconWidth() + 6,
267: icon.getIconHeight() + 4);
268: button.setPreferredSize(size);
269: button.setMargin(new Insets(2, 3, 2, 3));
270: toolbar.addSeparator(space);
271: toolbar.add(button);
272: }
273: }
274:
275: private Preferences getPreferences() {
276: return NbPreferences.forModule(FiltersManager.class);
277: }
278:
279: private JToggleButton createToggle(
280: Map<String, Boolean> fStates, int index) {
281: boolean isSelected = getPreferences().getBoolean(
282: filtersDesc.getName(index),
283: filtersDesc.isSelected(index));
284: Icon icon = filtersDesc.getSelectedIcon(index);
285: // ensure small size, just for the icon
286: JToggleButton result = new JToggleButton(icon, isSelected);
287: Dimension size = new Dimension(icon.getIconWidth() + 6,
288: icon.getIconHeight() + 4);
289: result.setPreferredSize(size);
290: result.setMargin(new Insets(2, 3, 2, 3));
291: result.setToolTipText(filtersDesc.getTooltip(index));
292: result.setFocusable(false);
293:
294: fStates.put(filtersDesc.getName(index), Boolean
295: .valueOf(isSelected));
296:
297: return result;
298: }
299:
300: /** Finds and returns index of filter with given name or -1 if no
301: * such filter exists.
302: */
303: private int filterIndexForName(String filterName) {
304: int filterCount = filtersDesc.getFilterCount();
305: String curName;
306: for (int i = 0; i < filterCount; i++) {
307: curName = filtersDesc.getName(i);
308: if (filterName.equals(curName)) {
309: return i;
310: }
311: }
312: return -1;
313: }
314:
315: /** Reactions to toggle button click, */
316: public void actionPerformed(ActionEvent e) {
317: // copy changed state first
318: JToggleButton toggle = (JToggleButton) e.getSource();
319: int index = toggles.indexOf(e.getSource());
320: synchronized (STATES_LOCK) {
321: filterStates.put(filtersDesc.getName(index), Boolean
322: .valueOf(toggle.isSelected()));
323: }
324: // notify
325: fireChange();
326: }
327:
328: private void fireChange() {
329: FilterChangeListener lCopy;
330: synchronized (L_LOCK) {
331: // no listener = no notification
332: if (clientL == null) {
333: return;
334: }
335: lCopy = clientL;
336: }
337:
338: // notify listener
339: lCopy.filterStateChanged(new ChangeEvent(
340: FiltersManager.this ));
341: }
342:
343: @Override
344: public void removeNotify() {
345: //remember filter settings
346: if (null != filterStates) {
347: Preferences prefs = getPreferences();
348: for (String filterName : filterStates.keySet()) {
349: prefs.putBoolean(filterName, filterStates
350: .get(filterName));
351: }
352: }
353: super .removeNotify();
354: }
355:
356: } // end of FiltersComponent
357:
358: }
|