001: package snow.texteditor;
002:
003: import snow.utils.gui.*;
004: import javax.swing.*;
005: import javax.swing.event.*;
006: import javax.swing.text.*;
007: import java.awt.event.*;
008: import java.util.*;
009: import java.awt.*;
010:
011: /** Small panel destined to be placed on the bottom (or top) of an editor pane.
012: * Pops up when CTRL+F is pressed. F4 searches the next occurence.
013: * Todo: implement replace.
014: */
015: public class SearchPanel extends JPanel {
016: final private JTextField searchTF = new JTextField(10);
017:
018: final private JButton searchPrevious = new JButton(
019: new Icons.UpWedge(10, 10, true));
020: final private JButton searchNext = new JButton(new Icons.DownWedge(
021: 10, 10, true));
022:
023: final private JButton close = new JButton(Icons.sharedCross);
024: final private JCheckBox searchFromLine = new JCheckBox(
025: "from actual pos", false);
026: final private JCheckBox ignoreCase = new JCheckBox("ignore case",
027: true);
028: final private JCheckBox backward = new JCheckBox("backward", false);
029: final private JTextField infoField = new JTextField(30);
030:
031: // Todo...
032: final private JCheckBox viewAll = new JCheckBox("all", false);
033: // TODO
034: //final private JCheckBox replace = new JCheckBox("replace", false);
035:
036: final private SimpleDocument doc;
037: final private JTextPane textPane;
038:
039: private int actualPosition = -1; // no actual pos
040: private String actualSearch = "";
041:
042: // used to avoid having a lot of highlights => always remove this before setting a new region as highlighted.
043: private Object actualHighlightTag = null;
044:
045: public static JPanel createSearchableTextPanePanel(JTextPane tp,
046: SimpleDocument doc) {
047: JPanel p = new JPanel(new BorderLayout(0, 0));
048: SearchPanel sp = new SearchPanel(tp, doc);
049: p.add(sp, BorderLayout.SOUTH);
050: p.add(new JScrollPane(tp), BorderLayout.CENTER);
051: return p;
052: }
053:
054: public SearchPanel(final JTextPane textPane, SimpleDocument doc) {
055: //super(new FlowLayout(FlowLayout.LEFT, 5, 1));
056: super ();
057: this .setLayout(new BoxLayout(this , BoxLayout.X_AXIS));
058: this
059: .setBorder(new CustomEtchedBorder(true, false, false,
060: false));
061:
062: this .doc = doc;
063: this .textPane = textPane;
064:
065: JPanel sPan = new JPanel(new FlowLayout(FlowLayout.LEFT, 4, 0));
066: add(sPan);
067: sPan.add(close);
068:
069: sPan.add(new JLabel("Search: "));
070: sPan.add(searchTF);
071: searchNext.setMargin(new Insets(0, 0, 0, 0));
072: sPan.add(searchNext);
073: searchNext.setToolTipText("Search next");
074: searchPrevious.setMargin(new Insets(0, 0, 0, 0));
075: sPan.add(searchPrevious);
076: searchPrevious.setToolTipText("Search previous");
077: sPan.add(ignoreCase);
078: sPan.add(searchFromLine);
079: sPan.add(backward);
080: //add(viewAll); // => highlight
081:
082: add(Box.createHorizontalStrut(10));
083: add(infoField);
084: infoField.setEditable(false);
085: infoField.setMargin(null);
086: infoField.setBorder(null);
087:
088: searchTF.addKeyListener(new KeyAdapter() {
089: @Override
090: public void keyReleased(KeyEvent ke) {
091: if (ke.getKeyCode() == KeyEvent.VK_ENTER) {
092: // search next
093: searchNext();
094: } else {
095: // reset the search
096: actualPosition = -1;
097: searchNext();
098: }
099: }
100: });
101:
102: textPane.registerKeyboardAction(new ActionListener() {
103: public void actionPerformed(ActionEvent ae) {
104: CTRL_F_pressed();
105: }
106: }, "Search", KeyStroke.getKeyStroke(KeyEvent.VK_F,
107: KeyEvent.CTRL_MASK, false),
108: JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
109:
110: textPane.registerKeyboardAction(new ActionListener() {
111: public void actionPerformed(ActionEvent ae) {
112: searchNext();
113: }
114: }, "SearchNext", KeyStroke
115: .getKeyStroke(KeyEvent.VK_F4, 0, true),
116: JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
117:
118: close.setMargin(new Insets(0, 0, 0, 0));
119: close.setFocusPainted(false);
120:
121: close.addActionListener(new ActionListener() {
122: public void actionPerformed(ActionEvent ae) {
123: setVisible(false);
124: textPane.requestFocus();
125: }
126: });
127:
128: searchNext.addActionListener(new ActionListener() {
129: public void actionPerformed(ActionEvent ae) {
130: searchNext(true);
131: }
132: });
133:
134: searchPrevious.addActionListener(new ActionListener() {
135: public void actionPerformed(ActionEvent ae) {
136: searchNext(false);
137: }
138: });
139:
140: // Turned on with CTRL+F
141: setVisible(false);
142: } // Constructor
143:
144: /** @param pos -1 to set from beginning
145: */
146: public void setSearchPositionStart(int pos) {
147: actualPosition = pos;
148: }
149:
150: public void setInfoText(final String info) {
151: if (EventQueue.isDispatchThread()) {
152: infoField.setText(info);
153: } else {
154: EventQueue.invokeLater(new Runnable() {
155: public void run() {
156: infoField.setText(info);
157: }
158: });
159: }
160: }
161:
162: public void CTRL_F_pressed() {
163: if (!this .isVisible()) {
164: this .setVisible(true);
165: }
166: searchTF.selectAll();
167: searchTF.requestFocus();
168: }
169:
170: public void searchNext() {
171: searchNext(!this .backward.isSelected());
172: }
173:
174: /** called either when enter typed OR when F3 has been pressed in the editor
175: */
176: public void searchNext(boolean forward) {
177: // if not visible, show the panel and return
178: if (!this .isVisible()) {
179: this .setVisible(true);
180: return;
181: }
182: searchTF.requestFocus();
183:
184: // remove highlight
185: if (actualHighlightTag != null) {
186: textPane.getHighlighter().removeHighlight(
187: actualHighlightTag);
188: }
189:
190: this .actualSearch = searchTF.getText();
191: if (ignoreCase.isSelected())
192: actualSearch = actualSearch.toUpperCase();
193: //System.out.println("Search "+this.actualSearch+" from "+actualPosition);
194: if (actualSearch.length() == 0)
195: return;
196:
197: int incr = 1;
198: if (!forward)
199: incr = -1;
200: if (this .searchFromLine.isSelected()) {
201: actualPosition = search(actualSearch, textPane
202: .getCaretPosition()
203: + incr, !forward);
204: } else {
205: actualPosition = search(actualSearch, this .actualPosition
206: + incr, !forward);
207: }
208:
209: if (actualPosition >= 0) {
210: searchTF.setBackground(UIManager
211: .getColor("TextField.background"));
212: // highlight result
213: //editorPanel.doc.setCharacterAttributes( actualPosition, actualSearch.length(), editorPanel.doc.getStyle("highlight"), true );
214:
215: try {
216: actualHighlightTag = textPane.getHighlighter()
217: .addHighlight(actualPosition,
218: actualPosition + actualSearch.length(),
219: doc.searchHighlighter);
220: // place the caret (important if the option search from actual position)
221: textPane.setCaretPosition(actualPosition);
222: // make that portion visible
223: //DocumentUtils.scrollToMiddle( textPane, actualPosition );
224: textPane.scrollRectToVisible(textPane
225: .modelToView(actualPosition));
226: } catch (Exception e) {
227: e.printStackTrace();
228: }
229: } else {
230: // not found => set as red
231: searchTF.setBackground(Color.red);
232: }
233: }
234:
235: private int search(String str, int from, boolean backward) {
236: if (backward) {
237: return doc.searchBackward(str, from, ignoreCase
238: .isSelected());
239: } else {
240: return doc.search(str, from, ignoreCase.isSelected());
241: }
242: }
243:
244: public void requestSearchFocus() {
245: searchTF.requestFocus();
246: }
247:
248: /** Special called from the editor popup to directly search the next.
249: * from <= 0 => finds the first
250: */
251: public void searchNext_calledFromPopup(String str, int from) {
252: // ??? some may not want to see this ??
253: this .setVisible(true);
254:
255: this .actualSearch = str;
256: this .searchTF.setText(str);
257: this .backward.setSelected(false);
258:
259: if (this .ignoreCase.isSelected())
260: str = str.toUpperCase();
261: // remove the old highlighted region
262: if (actualHighlightTag != null) {
263: textPane.getHighlighter().removeHighlight(
264: actualHighlightTag);
265: }
266:
267: int pos = doc.search(str, from, ignoreCase.isSelected());
268: if (pos >= 0) {
269: try {
270: actualHighlightTag = textPane.getHighlighter()
271: .addHighlight(pos, pos + str.length(),
272: doc.searchHighlighter);
273: // place the caret (important if the option search from actual position is active)
274: textPane.setCaretPosition(pos);
275: // make that portion visible
276: textPane.scrollRectToVisible(textPane.modelToView(pos));
277: } catch (Exception e) {
278: e.printStackTrace();
279: }
280: }
281: /*else
282: {
283: System.out.println(str+" not found from "+from);
284: }*/
285: }
286:
287: /** Special called from the editor popup to directly search the next.
288: * pass -1 to find the last.
289: */
290: public void searchPrevious_calledFromPopup(String str, int from) {
291: this .searchTF.setText(str);
292: this .backward.setSelected(true);
293:
294: if (this .ignoreCase.isSelected())
295: str = str.toUpperCase();
296: // remove old highlighted region
297: if (actualHighlightTag != null) {
298: textPane.getHighlighter().removeHighlight(
299: actualHighlightTag);
300: }
301:
302: int pos = doc
303: .searchBackward(str, from, ignoreCase.isSelected());
304: if (pos >= 0) {
305: try {
306: actualHighlightTag = textPane.getHighlighter()
307: .addHighlight(pos, pos + str.length(),
308: doc.searchHighlighter);
309: // place the caret (important if the option search from actual position)
310: textPane.setCaretPosition(pos);
311: // make that portion visible
312: textPane.scrollRectToVisible(textPane.modelToView(pos));
313: } catch (Exception e) {
314: e.printStackTrace();
315: }
316:
317: }
318: /*else
319: {
320: System.out.println(str+" not found backward from "+from);
321: }*/
322: }
323:
324: }
|