You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

SwingSearchBar.java 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441
  1. /*
  2. * Copyright (c) 2006-2010 Chris Smith, Shane Mc Cormack, Gregory Holmes
  3. *
  4. * Permission is hereby granted, free of charge, to any person obtaining a copy
  5. * of this software and associated documentation files (the "Software"), to deal
  6. * in the Software without restriction, including without limitation the rights
  7. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. * copies of the Software, and to permit persons to whom the Software is
  9. * furnished to do so, subject to the following conditions:
  10. *
  11. * The above copyright notice and this permission notice shall be included in
  12. * all copies or substantial portions of the Software.
  13. *
  14. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  20. * SOFTWARE.
  21. */
  22. package com.dmdirc.addons.ui_swing.components;
  23. import com.dmdirc.addons.ui_swing.components.frames.TextFrame;
  24. import com.dmdirc.addons.ui_swing.components.frames.InputTextFrame;
  25. import com.dmdirc.config.prefs.validator.ValidationResponse;
  26. import com.dmdirc.ui.IconManager;
  27. import com.dmdirc.ui.interfaces.SearchBar;
  28. import com.dmdirc.addons.ui_swing.UIUtilities;
  29. import com.dmdirc.addons.ui_swing.actions.SearchAction;
  30. import com.dmdirc.addons.ui_swing.components.validating.ValidatingJTextField;
  31. import com.dmdirc.addons.ui_swing.textpane.IRCDocument;
  32. import com.dmdirc.addons.ui_swing.textpane.IRCDocumentSearcher;
  33. import com.dmdirc.addons.ui_swing.textpane.LinePosition;
  34. import com.dmdirc.addons.ui_swing.textpane.TextPane;
  35. import com.dmdirc.config.IdentityManager;
  36. import com.dmdirc.config.prefs.validator.Validator;
  37. import com.dmdirc.interfaces.ConfigChangeListener;
  38. import com.dmdirc.util.ListenerList;
  39. import java.awt.Window;
  40. import java.awt.event.ActionEvent;
  41. import java.awt.event.ActionListener;
  42. import java.awt.event.KeyEvent;
  43. import java.awt.event.KeyListener;
  44. import javax.swing.JButton;
  45. import javax.swing.JCheckBox;
  46. import javax.swing.JComponent;
  47. import javax.swing.JOptionPane;
  48. import javax.swing.JPanel;
  49. import javax.swing.KeyStroke;
  50. import javax.swing.SwingUtilities;
  51. import javax.swing.event.DocumentEvent;
  52. import javax.swing.event.DocumentListener;
  53. import net.miginfocom.swing.MigLayout;
  54. /**
  55. * Status bar, shows message and info on the gui.
  56. */
  57. public final class SwingSearchBar extends JPanel implements ActionListener,
  58. KeyListener, SearchBar, DocumentListener, ConfigChangeListener {
  59. /**
  60. * A version number for this class. It should be changed whenever the class
  61. * structure is changed (or anything else that would prevent serialized
  62. * objects being unserialized with the new class).
  63. */
  64. private static final long serialVersionUID = 6;
  65. /** Frame parent. */
  66. private final TextFrame parent;
  67. /** Close button. */
  68. private ImageButton closeButton;
  69. /** Next match button. */
  70. private JButton nextButton;
  71. /** Previous match button. */
  72. private JButton prevButton;
  73. /** Case sensitive checkbox. */
  74. private JCheckBox caseCheck;
  75. /** Search text field. */
  76. private ValidatingJTextField searchBox;
  77. /** Line to search from. */
  78. private int line;
  79. /** Listener list. */
  80. private final ListenerList listeners;
  81. /** Parent window. */
  82. private Window parentWindow;
  83. /** Search validate text. */
  84. private SearchValidator validator;
  85. /**
  86. * Creates a new instance of StatusBar.
  87. *
  88. * @param newParent parent frame for the dialog
  89. * @param parentWindow Parent window
  90. */
  91. public SwingSearchBar(final TextFrame newParent, final Window parentWindow) {
  92. super();
  93. listeners = new ListenerList();
  94. this.parent = newParent;
  95. this.parentWindow = parentWindow;
  96. getInputMap(JComponent.WHEN_FOCUSED).
  97. put(KeyStroke.getKeyStroke(KeyEvent.VK_F3, 0), "searchAction");
  98. getActionMap().put("searchAction", new SearchAction(this));
  99. initComponents();
  100. layoutComponents();
  101. addListeners();
  102. }
  103. /** Initialises components. */
  104. private void initComponents() {
  105. closeButton = new ImageButton("close",
  106. IconManager.getIconManager().getIcon("close-inactive"),
  107. IconManager.getIconManager().getIcon("close-active"));
  108. nextButton = new JButton();
  109. prevButton = new JButton();
  110. caseCheck = new JCheckBox();
  111. validator = new SearchValidator();
  112. searchBox = new ValidatingJTextField(validator);
  113. nextButton.setText("Later");
  114. prevButton.setText("Earlier");
  115. nextButton.setEnabled(false);
  116. prevButton.setEnabled(false);
  117. caseCheck.setText("Case sensitive");
  118. line = -1;
  119. setColours();
  120. }
  121. /** Lays out components. */
  122. private void layoutComponents() {
  123. this.setLayout(new MigLayout("ins 0, fill"));
  124. add(closeButton);
  125. add(searchBox, "growx, pushx, sgy all");
  126. add(prevButton, "sgx button, sgy all");
  127. add(nextButton, "sgx button, sgy all");
  128. add(caseCheck, "sgy all");
  129. }
  130. /** Adds listeners to components. */
  131. private void addListeners() {
  132. closeButton.addActionListener(this);
  133. searchBox.addKeyListener(this);
  134. nextButton.addActionListener(this);
  135. prevButton.addActionListener(this);
  136. caseCheck.addActionListener(this);
  137. searchBox.getDocument().addDocumentListener(this);
  138. IdentityManager.getGlobalConfig().addChangeListener(
  139. "ui", "backgroundcolour", this);
  140. IdentityManager.getGlobalConfig().addChangeListener(
  141. "ui", "foregroundcolour", this);
  142. }
  143. /**
  144. * {@inheritDoc}.
  145. *
  146. * @param e Action event
  147. */
  148. @Override
  149. public void actionPerformed(final ActionEvent e) {
  150. if (e.getSource() == closeButton) {
  151. close();
  152. } else if (e.getSource() == nextButton) {
  153. search(Direction.DOWN, searchBox.getText(), caseCheck.isSelected());
  154. } else if (e.getSource() == prevButton) {
  155. search(Direction.UP, searchBox.getText(), caseCheck.isSelected());
  156. } else if (e.getSource() == caseCheck) {
  157. validator.setValidates(true);
  158. searchBox.checkError();
  159. line = parent.getTextPane().getLastVisibleLine();
  160. }
  161. }
  162. /** {@inheritDoc}. */
  163. @Override
  164. public void open() {
  165. SwingUtilities.invokeLater(new Runnable() {
  166. /** {@inheritDoc} */
  167. @Override
  168. public void run() {
  169. validator.setValidates(true);
  170. searchBox.checkError();
  171. setVisible(true);
  172. getFocus();
  173. }
  174. });
  175. }
  176. /** {@inheritDoc}. */
  177. @Override
  178. public void close() {
  179. SwingUtilities.invokeLater(new Runnable() {
  180. /** {@inheritDoc} */
  181. @Override
  182. public void run() {
  183. setVisible(false);
  184. if (parent instanceof InputTextFrame) {
  185. ((InputTextFrame) parent).getInputField().
  186. requestFocusInWindow();
  187. } else {
  188. parent.requestFocusInWindow();
  189. }
  190. }
  191. });
  192. }
  193. /** {@inheritDoc}. */
  194. @Override
  195. public void search(final String text, final boolean caseSensitive) {
  196. if (!searchBox.getText().isEmpty()) {
  197. if (line == -1) {
  198. line = parent.getTextPane().getLastVisibleLine();
  199. }
  200. search(Direction.UP, text, caseSensitive);
  201. }
  202. }
  203. /** {@inheritDoc}. */
  204. @Override
  205. public void search(final Direction direction, final String text,
  206. final boolean caseSensitive) {
  207. boolean foundText = false;
  208. final boolean up = Direction.UP == direction;
  209. final TextPane textPane = parent.getTextPane();
  210. final IRCDocument document = textPane.getDocument();
  211. final IRCDocumentSearcher searcher = new IRCDocumentSearcher(text,
  212. document,
  213. caseSensitive);
  214. searcher.setPosition(textPane.getSelectedRange());
  215. final LinePosition result = up ? searcher.searchUp() : searcher.
  216. searchDown();
  217. if (result == null) {
  218. foundText = false;
  219. } else if ((textPane.getSelectedRange().getEndLine() != 0 || textPane.
  220. getSelectedRange().getEndPos() != 0)
  221. && ((up && result.getEndLine() > textPane.getSelectedRange().
  222. getEndLine())
  223. || (!up && result.getStartLine() < textPane.getSelectedRange().
  224. getStartLine()))
  225. && JOptionPane.showConfirmDialog(parentWindow,
  226. "Do you want to continue searching from the " + (up ? "end"
  227. : "beginning") + "?",
  228. "No more results", JOptionPane.OK_CANCEL_OPTION,
  229. JOptionPane.QUESTION_MESSAGE) != JOptionPane.OK_OPTION) {
  230. // It's wrapped, and they don't want to continue searching
  231. foundText = false;
  232. } else {
  233. //found, select and return found
  234. textPane.setScrollBarPosition(result.getEndLine());
  235. textPane.setSelectedTexT(result);
  236. foundText = true;
  237. }
  238. validator.setValidates(foundText);
  239. searchBox.checkError();
  240. }
  241. /**
  242. * {@inheritDoc}.
  243. *
  244. * @param event Key event
  245. */
  246. @Override
  247. public void keyPressed(final KeyEvent event) {
  248. if (event.getSource() == searchBox.getTextField()) {
  249. if (event.getKeyCode() == KeyEvent.VK_ESCAPE) {
  250. close();
  251. } else if (event.getKeyCode() == KeyEvent.VK_ENTER) {
  252. search(Direction.UP, searchBox.getText(), caseCheck.isSelected());
  253. } else if (event.getKeyCode() != KeyEvent.VK_F3 && event.
  254. getKeyCode() != KeyEvent.VK_F) {
  255. line = parent.getTextPane().getLastVisibleLine();
  256. }
  257. }
  258. for (KeyListener listener : listeners.get(KeyListener.class)) {
  259. listener.keyPressed(event);
  260. }
  261. }
  262. /**
  263. * {@inheritDoc}.
  264. *
  265. * @param event Key event
  266. */
  267. @Override
  268. public void keyTyped(final KeyEvent event) {
  269. //Ignore
  270. }
  271. /**
  272. * {@inheritDoc}.
  273. *
  274. * @param event Key event
  275. */
  276. @Override
  277. public void keyReleased(final KeyEvent event) {
  278. //Ignore
  279. }
  280. /** Focuses the search box in the search bar. */
  281. public void getFocus() {
  282. SwingUtilities.invokeLater(new Runnable() {
  283. /** {@inheritDoc} */
  284. @Override
  285. public void run() {
  286. searchBox.requestFocusInWindow();
  287. searchBox.setSelectionStart(0);
  288. searchBox.setSelectionEnd(searchBox.getText().length());
  289. }
  290. });
  291. }
  292. /** {@inheritDoc}. */
  293. @Override
  294. public String getSearchPhrase() {
  295. return searchBox.getText();
  296. }
  297. /** {@inheritDoc}. */
  298. @Override
  299. public boolean isCaseSensitive() {
  300. return caseCheck.isSelected();
  301. }
  302. /** {@inheritDoc}. */
  303. @Override
  304. public void insertUpdate(final DocumentEvent e) {
  305. validator.setValidates(true);
  306. searchBox.checkError();
  307. nextButton.setEnabled(!searchBox.getText().isEmpty());
  308. prevButton.setEnabled(!searchBox.getText().isEmpty());
  309. }
  310. /** {@inheritDoc}. */
  311. @Override
  312. public void removeUpdate(final DocumentEvent e) {
  313. validator.setValidates(true);
  314. searchBox.checkError();
  315. nextButton.setEnabled(!searchBox.getText().isEmpty());
  316. prevButton.setEnabled(!searchBox.getText().isEmpty());
  317. }
  318. /** {@inheritDoc}. */
  319. @Override
  320. public void changedUpdate(final DocumentEvent e) {
  321. //Ignore
  322. }
  323. /** {@inheritDoc} */
  324. @Override
  325. public void addKeyListener(final KeyListener l) {
  326. UIUtilities.invokeLater(new Runnable() {
  327. /** {@inheritDoc} */
  328. @Override
  329. public void run() {
  330. listeners.add(KeyListener.class, l);
  331. }
  332. });
  333. }
  334. /** {@inheritDoc} */
  335. @Override
  336. public void removeKeyListener(final KeyListener l) {
  337. UIUtilities.invokeLater(new Runnable() {
  338. /** {@inheritDoc} */
  339. @Override
  340. public void run() {
  341. listeners.remove(KeyListener.class, l);
  342. }
  343. });
  344. }
  345. /** {@inheritDoc} */
  346. @Override
  347. public void configChanged(final String domain, final String key) {
  348. setColours();
  349. }
  350. private void setColours() {
  351. searchBox.setForeground(IdentityManager.getGlobalConfig().
  352. getOptionColour("ui", "foregroundcolour"));
  353. searchBox.setBackground(IdentityManager.getGlobalConfig().
  354. getOptionColour("ui", "backgroundcolour"));
  355. searchBox.setCaretColor(IdentityManager.getGlobalConfig().
  356. getOptionColour("ui", "foregroundcolour"));
  357. }
  358. }
  359. /**
  360. * Simple search validator.
  361. */
  362. class SearchValidator implements Validator<String> {
  363. /**
  364. * A version number for this class. It should be changed whenever the class
  365. * structure is changed (or anything else that would prevent serialized
  366. * objects being unserialized with the new class).
  367. */
  368. private static final long serialVersionUID = 1;
  369. /** Validates. */
  370. private boolean validates = true;
  371. /** {@inheritDoc} */
  372. @Override
  373. public ValidationResponse validate(final String object) {
  374. if (validates) {
  375. return new ValidationResponse();
  376. }
  377. return new ValidationResponse("Not found.");
  378. }
  379. /**
  380. * Sets whether this validator validates.
  381. * @param validates
  382. */
  383. public void setValidates(final boolean validates) {
  384. this.validates = validates;
  385. }
  386. }