001    /*
002     * $Id: JXFindPanel.java 3357 2009-05-30 04:20:07Z kschaefe $
003     *
004     * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
005     * Santa Clara, California 95054, U.S.A. All rights reserved.
006     *
007     * This library is free software; you can redistribute it and/or
008     * modify it under the terms of the GNU Lesser General Public
009     * License as published by the Free Software Foundation; either
010     * version 2.1 of the License, or (at your option) any later version.
011     * 
012     * This library is distributed in the hope that it will be useful,
013     * but WITHOUT ANY WARRANTY; without even the implied warranty of
014     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015     * Lesser General Public License for more details.
016     * 
017     * You should have received a copy of the GNU Lesser General Public
018     * License along with this library; if not, write to the Free Software
019     * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
020     */
021    
022    package org.jdesktop.swingx;
023    
024    import java.awt.Component;
025    import java.util.Locale;
026    import java.util.regex.Pattern;
027    
028    import javax.swing.Box;
029    import javax.swing.BoxLayout;
030    import javax.swing.JCheckBox;
031    import javax.swing.JLabel;
032    import javax.swing.JOptionPane;
033    
034    import org.jdesktop.swingx.search.PatternModel;
035    import org.jdesktop.swingx.search.Searchable;
036    
037    /**
038     * {@code JXFindPanel} is a basic find panel suitable for use in dialogs. It
039     * offers case-sensitivity, wrapped searching, and reverse searching.
040     * 
041     * @author unascribed from JDNC
042     * @author Jeanette Winzenburg
043     */
044    public class JXFindPanel extends AbstractPatternPanel {
045    
046        public static final String FIND_NEXT_ACTION_COMMAND = "findNext";
047        public static final String FIND_PREVIOUS_ACTION_COMMAND = "findPrevious";
048        
049        protected Searchable searchable;
050    
051        protected JCheckBox wrapCheck;
052        protected JCheckBox backCheck;
053        private boolean initialized;
054    
055        /**
056         * Default constructor for the find panel. Constructs panel not targeted to
057         * any component.
058         */
059        public JXFindPanel() {
060            this(null);
061        }
062        
063        /**
064         * Construct search panel targeted to specific <code>Searchable</code> component.
065         *
066         * @param searchible Component where search widget will try to locate and select
067         *                   information using methods of the <code>Searchible</code> interface.
068         */
069        public JXFindPanel(Searchable searchable) {
070            setName(getUIString(SEARCH_TITLE));
071            setSearchable(searchable);
072            initActions();
073        }
074        
075        /**
076         * Sets the Searchable targeted of this find widget.
077         * Triggers a search with null pattern to release the old
078         * searchable, if any.
079         * 
080         * @param searchable Component where search widget will try to locate and select
081         *                   information using methods of the {@link Searchable Searchable} interface.
082         */
083        public void setSearchable(Searchable searchable) {
084            if ((this.searchable != null) && this.searchable.equals(searchable)) return;
085            Searchable old = this.searchable;
086            if (old != null) {
087                old.search((Pattern) null);
088            }
089            this.searchable = searchable;
090            getPatternModel().setFoundIndex(-1);
091            firePropertyChange("searchable", old, this.searchable);
092        }
093        
094        /**
095         * Notifies this component that it now has a parent component.
096         * When this method is invoked, the chain of parent components is
097         * set up with <code>KeyboardAction</code> event listeners.
098         */
099        @Override
100        public void addNotify() {
101            init();
102            super.addNotify();
103        }
104        
105       /**
106        * Initializes component and its listeners and models.
107        */ 
108        protected void init() {
109            if (initialized) return;
110            initialized = true;
111            initComponents();
112            build();
113            bind();
114        }
115        
116        //------------------ support synch the model <--> components
117        
118    
119        /**
120         * Configure and bind components to/from PatternModel.
121         */
122        @Override
123        protected void bind() {
124            super.bind();
125            getActionContainerFactory().configureButton(wrapCheck, 
126                    getAction(PatternModel.MATCH_WRAP_ACTION_COMMAND),
127                    null);
128            getActionContainerFactory().configureButton(backCheck, 
129                    getAction(PatternModel.MATCH_BACKWARDS_ACTION_COMMAND),
130                    null);
131        }
132    
133        
134        /**
135         * called from listening to empty property of PatternModel.
136         * 
137         * this implementation calls super and additionally synchs the 
138         * enabled state of FIND_NEXT_ACTION_COMMAND, FIND_PREVIOUS_ACTION_COMMAND
139         * to !empty.
140         */
141        @Override
142        protected void refreshEmptyFromModel() {
143            super.refreshEmptyFromModel();
144            boolean enabled = !getPatternModel().isEmpty();
145            getAction(FIND_NEXT_ACTION_COMMAND).setEnabled(enabled);
146            getAction(FIND_PREVIOUS_ACTION_COMMAND).setEnabled(enabled);
147        }
148    
149        //--------------------- action callbacks
150        /**
151         * Action callback for Find action.
152         * Find next/previous match using current setting of direction flag.
153         * 
154         */
155        @Override
156        public void match() {
157            doFind();
158        }
159    
160        /**
161         * Action callback for FindNext action.
162         * Sets direction flag to forward and calls find.
163         */
164        public void findNext() {
165            getPatternModel().setBackwards(false);
166            doFind();
167        }
168        
169        /**
170         * Action callback for FindPrevious action.
171         * Sets direction flag to previous and calls find.
172         */
173        public void findPrevious() {
174            getPatternModel().setBackwards(true);
175            doFind();
176        }
177        
178        /**
179         * Common standalone method to perform search. Used by the action callback methods 
180         * for Find/FindNext/FindPrevious actions. Finds next/previous match using current 
181         * setting of direction flag. Result is being reporred using showFoundMessage and 
182         * showNotFoundMessage methods respectively.
183         *
184         * @see #match
185         * @see #findNext
186         * @see #findPrevious
187         */
188        protected void doFind() {
189            if (searchable == null)
190                return;
191            int foundIndex = doSearch();
192            boolean notFound = (foundIndex == -1) && !getPatternModel().isEmpty();
193            if (notFound) {
194                if (getPatternModel().isWrapping()) {
195                    notFound = doSearch() == -1;
196                }
197            }
198            if (notFound) {
199                showNotFoundMessage();
200            } else {
201                showFoundMessage();
202            }
203        }
204    
205        /**
206         * Performs search and returns index of the next match.
207         *
208         * @return Index of the next match in document.
209         */
210        protected int doSearch() {
211            int foundIndex = searchable.search(getPatternModel().getPattern(), 
212                    getPatternModel().getFoundIndex(), getPatternModel().isBackwards());
213            getPatternModel().setFoundIndex(foundIndex);
214            return getPatternModel().getFoundIndex();
215    //         first try on #236-swingx - foundIndex wrong in backwards search.
216    //         re-think: autoIncrement in PatternModel? 
217    //        return foundIndex; 
218        }
219    
220        /**
221         * Report that suitable match is found.
222         */
223        protected void showFoundMessage() {
224            
225        }
226    
227        /**
228         * Report that no match is found.
229         */
230        protected void showNotFoundMessage() {
231            JOptionPane.showMessageDialog(this, getUIString("notFound"));
232        }
233    
234        
235    //-------------- dynamic Locale support
236        
237        
238        
239        @Override
240        protected void updateLocaleState(Locale locale) {
241            super.updateLocaleState(locale);
242            setName(getUIString(SEARCH_TITLE, locale));
243        }
244        
245        //-------------------------- initial
246        
247    
248        /**
249         * creates and registers all "executable" actions.
250         * Meaning: the actions bound to a callback method on this.
251         */
252        @Override
253        protected void initExecutables() {
254            getActionMap().put(FIND_NEXT_ACTION_COMMAND, 
255                    createBoundAction(FIND_NEXT_ACTION_COMMAND, "findNext"));
256            getActionMap().put(FIND_PREVIOUS_ACTION_COMMAND, 
257                    createBoundAction(FIND_PREVIOUS_ACTION_COMMAND, "findPrevious"));
258            super.initExecutables();
259        }
260    
261    
262      
263    //----------------------------- init ui
264        
265        /** 
266         * Create and initialize components.
267         */
268        @Override
269        protected void initComponents() {
270            super.initComponents();
271            wrapCheck = new JCheckBox();
272            backCheck = new JCheckBox();
273        }
274    
275    
276    
277        /**
278         * Compose and layout all the subcomponents.
279         */
280        protected void build() {
281            Box lBox = new Box(BoxLayout.LINE_AXIS); 
282            lBox.add(searchLabel);
283            lBox.add(new JLabel(":"));
284            lBox.add(new JLabel("  "));
285            lBox.setAlignmentY(Component.TOP_ALIGNMENT);
286            Box rBox = new Box(BoxLayout.PAGE_AXIS); 
287            rBox.add(searchField);
288            rBox.add(matchCheck);
289            rBox.add(wrapCheck);
290            rBox.add(backCheck);
291            rBox.setAlignmentY(Component.TOP_ALIGNMENT);
292    
293            setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS));
294            
295            add(lBox);
296            add(rBox);
297        }
298    
299    }