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