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