/plugins/OpenIt/tags/OpenIt-1_04/src/org/etheridge/openit/gui/FindFileWindow.java

# · Java · 662 lines · 438 code · 91 blank · 133 comment · 61 complexity · 6be16b23d5765987269780987b54ede0 MD5 · raw file

  1. /*
  2. * OpenIt jEdit Plugin (FindFileWindow.java)
  3. *
  4. * Copyright (C) 2003 Matt Etheridge (matt@etheridge.org)
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU General Public License
  8. * as published by the Free Software Foundation; either version 2
  9. * of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this program; if not, write to the Free Software
  18. * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  19. */
  20. package org.etheridge.openit.gui;
  21. import gnu.regexp.REException;
  22. import java.awt.BorderLayout;
  23. import java.awt.Color;
  24. import java.awt.event.ActionEvent;
  25. import java.awt.event.ActionListener;
  26. import java.awt.event.KeyAdapter;
  27. import java.awt.event.KeyEvent;
  28. import java.awt.event.KeyListener;
  29. import java.awt.event.MouseAdapter;
  30. import java.awt.event.MouseEvent;
  31. import java.awt.event.MouseMotionAdapter;
  32. import java.awt.event.WindowAdapter;
  33. import java.awt.event.WindowEvent;
  34. import java.awt.Font;
  35. import java.awt.GridLayout;
  36. import java.awt.Insets;
  37. import java.awt.Point;
  38. import java.awt.Rectangle;
  39. import java.beans.PropertyChangeEvent;
  40. import java.beans.PropertyChangeListener;
  41. import java.util.ArrayList;
  42. import java.util.Iterator;
  43. import java.util.List;
  44. import javax.swing.AbstractAction;
  45. import javax.swing.border.BevelBorder;
  46. import javax.swing.BorderFactory;
  47. import javax.swing.event.DocumentEvent;
  48. import javax.swing.event.DocumentListener;
  49. import javax.swing.JButton;
  50. import javax.swing.JFrame;
  51. import javax.swing.JLabel;
  52. import javax.swing.JList;
  53. import javax.swing.JPanel;
  54. import javax.swing.JScrollPane;
  55. import javax.swing.JTextField;
  56. import javax.swing.JWindow;
  57. import javax.swing.KeyStroke;
  58. import javax.swing.ListSelectionModel;
  59. import javax.swing.SwingUtilities;
  60. import javax.swing.text.BadLocationException;
  61. import javax.swing.ToolTipManager;
  62. import org.etheridge.openit.OpenItProperties;
  63. import org.etheridge.openit.sourcepath.QuickAccessSourcePath;
  64. import org.etheridge.openit.sourcepath.SourcePathFile;
  65. import org.etheridge.openit.SourcePathManager;
  66. import org.gjt.sp.jedit.jEdit;
  67. /**
  68. * Popup window that allows users to search for files
  69. */
  70. public class FindFileWindow extends JFrame
  71. {
  72. // source file list GUI components
  73. private JWindow mSourceFileListWindow;
  74. private JList mSourceFileList;
  75. private SourceFileListModel mSourceFileListModel;
  76. private JScrollPane mScrollPane;
  77. private List mFileSelectionListeners;
  78. // source file name and filter GUI components
  79. private JTextField mSourceFileNameField;
  80. private JButton mFilterButton;
  81. private String mShowFilterString;
  82. private String mHideFilterString;
  83. private SourceFileFilter mSourceFileFilter;
  84. private FilterDialog mFilterDialog;
  85. // Loading label
  86. private JLabel mLoadingLabel;
  87. // create a file window that contains a default filter
  88. public FindFileWindow()
  89. {
  90. init();
  91. }
  92. //
  93. // Import Selection Listener Methods
  94. //
  95. public void addFileSelectionListener(FileSelectionListener listener)
  96. {
  97. if (mFileSelectionListeners == null) {
  98. mFileSelectionListeners = new ArrayList();
  99. }
  100. mFileSelectionListeners.add(listener);
  101. }
  102. public void removeFileSelectionListener(FileSelectionListener listener)
  103. {
  104. if (mFileSelectionListeners != null) {
  105. mFileSelectionListeners.remove(listener);
  106. }
  107. }
  108. /**
  109. * Overridden to get around focusing problem!
  110. * NOTE: if anyone knows how to get around this bloody problem, let me know! ;)
  111. */
  112. public void setVisible(boolean visible)
  113. {
  114. super.setVisible(visible);
  115. // after the top level JFrame has been made visible, we can then set the
  116. // location of the SourceFileListWindow (JWindow) underneath the JFrame.
  117. Rectangle findFileWindowBounds = getBounds();
  118. mSourceFileListWindow.setLocation
  119. (new Point((int) (findFileWindowBounds.getX()),
  120. (int) (findFileWindowBounds.getY() + findFileWindowBounds.getHeight())));
  121. if (visible && mSourceFileListModel.getSize() > 0) {
  122. mSourceFileListWindow.setVisible(visible);
  123. } else {
  124. mSourceFileListWindow.setVisible(false);
  125. }
  126. // If the filter is showing, then bring it to the front
  127. if (mFilterButton.getActionCommand().equals(mHideFilterString)) {
  128. mFilterDialog.setVisible(visible);
  129. }
  130. mFilterDialog.toFront();
  131. // make the text field get focus
  132. mSourceFileNameField.requestFocus();
  133. }
  134. /**
  135. * Clear any source files currently in the list.
  136. */
  137. public void clearSourceFiles()
  138. {
  139. // if we are loading, ignore this request (as we do not want to remove
  140. // the loading label!)
  141. if (SourcePathManager.staticGetQuickAccessSourcePath() != null) {
  142. mSourceFileNameField.setText("");
  143. }
  144. }
  145. public void selectFile(String fileName)
  146. {
  147. if (fileName != null) {
  148. mSourceFileNameField.setText(fileName);
  149. }
  150. }
  151. //
  152. // Private Helper Methods
  153. //
  154. private void setSourceFiles(List sourceFiles)
  155. {
  156. mSourceFileFilter.filter(sourceFiles);
  157. mSourceFileListModel.refreshModel(sourceFiles);
  158. mSourceFileList.setSelectedIndex(0);
  159. // if there are no source files to show, then hide the file list
  160. if (sourceFiles.isEmpty()) {
  161. mSourceFileListWindow.setVisible(false);
  162. }
  163. // otherwise, only show the rows that exist (up to a minimum of 8 at a
  164. // time in the scroll pane).
  165. else {
  166. mSourceFileList.setVisibleRowCount(Math.min(sourceFiles.size(), 8));
  167. mSourceFileListWindow.pack();
  168. mSourceFileListWindow.setVisible(true);
  169. }
  170. mSourceFileList.invalidate();
  171. mScrollPane.revalidate();
  172. mScrollPane.repaint();
  173. }
  174. /**
  175. * Initialise the GUI components
  176. */
  177. private void init()
  178. {
  179. // initialize static button string contants
  180. mShowFilterString = jEdit.getProperty("openit.FindFileWindow.FilterButton.Show.label");
  181. mHideFilterString = jEdit.getProperty("openit.FindFileWindow.FilterButton.Hide.label");
  182. getContentPane().setLayout(new BorderLayout());
  183. // remove the JFrame top section so it looks like a window
  184. setUndecorated(true);
  185. // create import list
  186. mSourceFileListModel = new SourceFileListModel();
  187. mSourceFileList = new JList(mSourceFileListModel);
  188. mSourceFileList.setBorder(BorderFactory.createEtchedBorder());
  189. // escape listener (to close the window when escape is pressed).
  190. KeyStroke ks = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, false);
  191. mSourceFileList.getInputMap().put(ks, ks);
  192. mSourceFileList.getActionMap().put(ks, new AbstractAction()
  193. {
  194. public void actionPerformed(ActionEvent ae)
  195. {
  196. closeWindow();
  197. }
  198. });
  199. // enter listener (to signify a user pressed enter to select an import statement)
  200. KeyStroke enterKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, false);
  201. mSourceFileList.getInputMap().put(enterKeyStroke, enterKeyStroke);
  202. mSourceFileList.getActionMap().put(enterKeyStroke, new AbstractAction()
  203. {
  204. public void actionPerformed(ActionEvent ae)
  205. {
  206. selectionMade();
  207. }
  208. });
  209. // add a mouse listener for user selection
  210. mSourceFileList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
  211. mSourceFileList.addMouseListener(new MouseAdapter()
  212. {
  213. public void mouseClicked(MouseEvent e)
  214. {
  215. // double-click signifies a user selection
  216. if (e.getClickCount() >= 2) {
  217. selectionMade();
  218. }
  219. }
  220. });
  221. // add renderer
  222. mSourceFileList.setCellRenderer(new SourceFileListCellRenderer());
  223. // add a source file filter and dialog
  224. if (mSourceFileFilter == null) {
  225. mSourceFileFilter = new SourceFileFilter();
  226. }
  227. mFilterDialog = new FilterDialog();
  228. mFilterDialog.addPropertyChangeListener(new PropertyChangeListener()
  229. {
  230. public void propertyChange(PropertyChangeEvent evt)
  231. {
  232. String regularExpression = (String) evt.getNewValue();
  233. try {
  234. mSourceFileFilter.setRegularExpressionString(regularExpression);
  235. updateList(mSourceFileNameField.getText());
  236. } catch (REException ree) {
  237. mSourceFileFilter.clearRegularExpression();
  238. ree.printStackTrace();
  239. }
  240. }
  241. });
  242. mScrollPane = new JScrollPane(mSourceFileList);
  243. getContentPane().add(createClassNamePanel(), BorderLayout.NORTH);
  244. // add teh source file list in a new window
  245. mSourceFileListWindow = new JWindow();
  246. mSourceFileListWindow.getContentPane().setLayout(new BorderLayout());
  247. mSourceFileListWindow.getContentPane().add(mScrollPane);
  248. mSourceFileListWindow.pack();
  249. // create the loading thread if required
  250. if (SourcePathManager.staticGetQuickAccessSourcePath() == null) {
  251. createLoaderThread();
  252. }
  253. //
  254. // key listeners - this is done here once all components are initialized
  255. //
  256. // add a key listener to the source file list - any keys that should not
  257. // be handled by the soure file list and dispatched to the source file
  258. // name text field for handling there.
  259. mSourceFileList.addKeyListener(new KeyAdapterProxy(mSourceFileNameField)
  260. {
  261. protected boolean shouldForwardEvent(KeyEvent e)
  262. {
  263. return !isKeyHandledBySourceFileList(e.getKeyCode());
  264. }
  265. });
  266. // add another key listener to the source file list to handle the beginning
  267. // and end of lists.
  268. mSourceFileList.addKeyListener(new KeyAdapter()
  269. {
  270. private boolean mDownAtEndPressed = false;
  271. private boolean mUpPressedAtBeginning = false;
  272. public void keyPressed(KeyEvent e)
  273. {
  274. handleEvent(e);
  275. }
  276. public void keyReleased(KeyEvent e)
  277. {
  278. handleEvent(e);
  279. }
  280. private void handleEvent(KeyEvent e)
  281. {
  282. if (downPressedAtEnd(e)) {
  283. if (mDownAtEndPressed) {
  284. mSourceFileList.setSelectedIndex(0);
  285. mSourceFileList.ensureIndexIsVisible(0);
  286. mDownAtEndPressed = false;
  287. e.consume();
  288. } else {
  289. mDownAtEndPressed = true;
  290. }
  291. mUpPressedAtBeginning = false;
  292. } else if (upPressedAtBeginning(e)) {
  293. if (mUpPressedAtBeginning) {
  294. mSourceFileList.setSelectedIndex(mSourceFileList.getModel().getSize()-1);
  295. mSourceFileList.ensureIndexIsVisible(mSourceFileList.getModel().getSize()-1);
  296. mUpPressedAtBeginning = false;
  297. e.consume();
  298. } else {
  299. mUpPressedAtBeginning = true;
  300. }
  301. mDownAtEndPressed = false;
  302. } else {
  303. // neither up or down was pressed at beginnig or end
  304. mUpPressedAtBeginning = false;
  305. mDownAtEndPressed = false;
  306. }
  307. }
  308. private boolean upPressedAtBeginning(KeyEvent e)
  309. {
  310. return (e.getKeyCode() == KeyEvent.VK_UP || e.getKeyCode() == KeyEvent.VK_PAGE_UP)
  311. && (mSourceFileList.getSelectedIndex() == 0);
  312. }
  313. private boolean downPressedAtEnd(KeyEvent e)
  314. {
  315. return (e.getKeyCode() == KeyEvent.VK_DOWN || e.getKeyCode() == KeyEvent.VK_PAGE_DOWN)
  316. && (mSourceFileList.getSelectedIndex() == mSourceFileList.getModel().getSize()-1);
  317. }
  318. });
  319. // add a key listener to the filter button, so ALL key events are forwarded
  320. // to the name field.
  321. mFilterButton.addKeyListener(new KeyAdapterProxy(mSourceFileNameField));
  322. // certain key events (up,down,enter) should be dispatched onto the
  323. // class list for movement and selecting
  324. mSourceFileNameField.addKeyListener(new KeyAdapterProxy(mSourceFileList)
  325. {
  326. protected boolean shouldForwardEvent(KeyEvent e)
  327. {
  328. return isKeyHandledBySourceFileList(e.getKeyCode());
  329. }
  330. });
  331. // add a window listener to this JFrame, so that if it is iconified/deiconified
  332. // the file list is hidden/shown
  333. addWindowListener(new WindowAdapter()
  334. {
  335. public void windowIconified(WindowEvent e)
  336. {
  337. mSourceFileListWindow.setVisible(false);
  338. }
  339. public void windowDeiconified(WindowEvent e)
  340. {
  341. mSourceFileListWindow.setVisible(true);
  342. }
  343. });
  344. pack();
  345. mFilterDialog.notifyListeners();
  346. }
  347. /**
  348. * Creates a text field for users to enter class names in
  349. */
  350. private JPanel createClassNamePanel()
  351. {
  352. // create panel, with GridLayout
  353. JPanel classNamePanel = new JPanel(new GridLayout(2,1));
  354. classNamePanel.setBorder(BorderFactory.createCompoundBorder(
  355. BorderFactory.createLineBorder(Color.lightGray),
  356. BorderFactory.createBevelBorder(BevelBorder.RAISED, Color.lightGray, Color.darkGray)));
  357. // instruction label and filter panel
  358. JPanel labelAndFilterPanel = new JPanel(new BorderLayout());
  359. JLabel instructionLabel = new JLabel(jEdit.getProperty("openit.FindFileWindow.Instruction.label"));
  360. instructionLabel.setFont(new Font("dialog", Font.BOLD, 12));
  361. instructionLabel.setForeground(Color.black);
  362. instructionLabel.setBorder(BorderFactory.createEmptyBorder(2,2,2,2));
  363. labelAndFilterPanel.add(instructionLabel, BorderLayout.WEST);
  364. labelAndFilterPanel.add(createFilterPanel(), BorderLayout.CENTER);
  365. // the actual text field
  366. mSourceFileNameField = new JTextField();
  367. mSourceFileNameField.setColumns(35);
  368. // add components to panel
  369. classNamePanel.add(labelAndFilterPanel);
  370. classNamePanel.add(mSourceFileNameField);
  371. // listen for changes to the document (the text) in the text field, and
  372. // reload the class list accordingly.
  373. mSourceFileNameField.getDocument().addDocumentListener(new DocumentListener()
  374. {
  375. public void changedUpdate(DocumentEvent e)
  376. {
  377. updateSourceFileList(e);
  378. }
  379. public void insertUpdate(DocumentEvent e)
  380. {
  381. updateSourceFileList(e);
  382. }
  383. public void removeUpdate(DocumentEvent e)
  384. {
  385. updateSourceFileList(e);
  386. }
  387. private void updateSourceFileList(DocumentEvent e)
  388. {
  389. // get document text
  390. String documentText = null;
  391. try {
  392. documentText = e.getDocument().getText(0, e.getDocument().getLength());
  393. } catch (BadLocationException ble) {
  394. // if we get this exception something really funky has gone wrong, so
  395. // we'll just return and do nothing.
  396. ble.printStackTrace();
  397. return;
  398. }
  399. updateList(documentText);
  400. }
  401. });
  402. // escape listener (to close the window when escape is pressed).
  403. KeyStroke ks = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, false);
  404. mSourceFileNameField.getInputMap().put(ks, ks);
  405. mSourceFileNameField.getActionMap().put(ks, new AbstractAction()
  406. {
  407. public void actionPerformed(ActionEvent ae)
  408. {
  409. closeWindow();
  410. }
  411. });
  412. return classNamePanel;
  413. }
  414. private JPanel createFilterPanel()
  415. {
  416. JPanel filterPanel = new JPanel(new BorderLayout());
  417. mFilterButton = new JButton(mShowFilterString);
  418. mFilterButton.setMargin(new Insets(1,1,1,1));
  419. mFilterButton.addActionListener(new ActionListener()
  420. {
  421. public void actionPerformed(ActionEvent e)
  422. {
  423. // position the filter dialog at the right, top corner of this window
  424. Rectangle currentWindowBounds = FindFileWindow.this.getBounds();
  425. mFilterDialog.setLocation(new Point((int) (currentWindowBounds.getX() + currentWindowBounds.getWidth()),
  426. (int) currentWindowBounds.getY()));
  427. // show the filter dialog
  428. mFilterDialog.setVisible(!mFilterDialog.isVisible());
  429. // change the button name
  430. String buttonName = mFilterDialog.isVisible() ? mHideFilterString : mShowFilterString;
  431. mFilterButton.setText(buttonName);
  432. }
  433. });
  434. //filterPanel.add(mLoadingLabel, BorderLayout.WEST);
  435. filterPanel.add(mFilterButton, BorderLayout.EAST);
  436. return filterPanel;
  437. }
  438. private void updateList(String documentText)
  439. {
  440. // if there is nothing in the document, then clear the source files
  441. // and return
  442. if (documentText.length() == 0) {
  443. setSourceFiles(new ArrayList()); // empty list
  444. return;
  445. }
  446. // determine starting letter
  447. char startingChar = documentText.charAt(0);
  448. // attempt to get teh quick access source path from the SourcePathManager
  449. QuickAccessSourcePath quickAccessSourcePath =
  450. SourcePathManager.staticGetQuickAccessSourcePath();
  451. // if the QuickAccessSourcePath instance is null (it can be null
  452. // if the initial creation thread has not finished yet), then just
  453. // return as we cannot do anything yet.
  454. if (quickAccessSourcePath == null) {
  455. return;
  456. }
  457. // TODO: depending on user configuration - this should do one or the other
  458. List sourceFilesStartingWithLetter = null;
  459. if (jEdit.getBooleanProperty(OpenItProperties.ALLOW_SUBSTRING_MATCHING, true)) {
  460. sourceFilesStartingWithLetter =
  461. new ArrayList(quickAccessSourcePath.getSourceFilesContaining(documentText));
  462. } else {
  463. sourceFilesStartingWithLetter =
  464. new ArrayList(quickAccessSourcePath.getSourceFilesStartingWith(startingChar));
  465. }
  466. // iterate through list and remove those source files that do not
  467. // start with the text in the source file name field
  468. for (Iterator i = sourceFilesStartingWithLetter.iterator(); i.hasNext();) {
  469. SourcePathFile currentSourcePathFile = (SourcePathFile) i.next();
  470. String currentFileName = currentSourcePathFile.getFullName();
  471. // if the match is *not* case sensitive then make the file name and
  472. // document text (to compare with) lowercase
  473. if (!jEdit.getBooleanProperty(OpenItProperties.POP_UP_CASE_SENSITIVE_FILE_MATCHING, false)) {
  474. currentFileName = currentFileName.toLowerCase();
  475. documentText = documentText.toLowerCase();
  476. }
  477. // if the user wants substring matching, then search for any substring
  478. // in the text, otherwise, just search for the first letter.
  479. if (jEdit.getBooleanProperty(OpenItProperties.ALLOW_SUBSTRING_MATCHING, true)) {
  480. if (currentFileName.indexOf(documentText) < 0) {
  481. i.remove();
  482. }
  483. } else {
  484. if (!currentFileName.startsWith(documentText)) {
  485. i.remove();
  486. }
  487. }
  488. }
  489. setSourceFiles(sourceFilesStartingWithLetter);
  490. }
  491. /**
  492. * @return whether or not the specified keycode is one that should be
  493. * handled by the class list
  494. */
  495. private boolean isKeyHandledBySourceFileList(int keyCode)
  496. {
  497. return (keyCode == KeyEvent.VK_UP ||
  498. keyCode == KeyEvent.VK_DOWN ||
  499. keyCode == KeyEvent.VK_ENTER ||
  500. keyCode == KeyEvent.VK_PAGE_UP ||
  501. keyCode == KeyEvent.VK_PAGE_DOWN);
  502. }
  503. /**
  504. * Notify any import selection listeners that an import statement was
  505. * selected.
  506. */
  507. private void notifyFileSelectionListeners(SourcePathFile sourceFile)
  508. {
  509. if (mFileSelectionListeners != null) {
  510. for (Iterator i = mFileSelectionListeners.iterator(); i.hasNext();) {
  511. FileSelectionListener listener = (FileSelectionListener) i.next();
  512. listener.fileSelected(sourceFile);
  513. }
  514. }
  515. }
  516. private void selectionMade()
  517. {
  518. if (!mSourceFileListModel.isEmpty()) {
  519. notifyFileSelectionListeners((SourcePathFile)mSourceFileList.getSelectedValue());
  520. closeWindow();
  521. }
  522. }
  523. private void closeWindow()
  524. {
  525. // hide this window
  526. hide();
  527. // and hide the filter dialog if it is showing
  528. mFilterDialog.hide();
  529. // hide the list window
  530. mSourceFileListWindow.hide();
  531. }
  532. private void createLoaderThread()
  533. {
  534. // set the loading text on the file name field
  535. mSourceFileNameField.setText(jEdit.getProperty("openit.FindFileWindow.InitialLoadingMessage.label"));
  536. // create and start the thread
  537. InitialLoadingThread loaderThread = new InitialLoadingThread();
  538. loaderThread.start();
  539. }
  540. //
  541. // Inner Classes
  542. //
  543. /**
  544. * Keeps checking to see whether the QuickAccessSourcePath is available, and
  545. * when it is, it updates clears the text on the file name field.
  546. * NOTE: could use a SwingWorker here
  547. */
  548. public class InitialLoadingThread extends Thread
  549. {
  550. public void run()
  551. {
  552. // periodicially check to see whether the QuickAccessSourcePath is
  553. // available yet.
  554. while (SourcePathManager.staticGetQuickAccessSourcePath() == null) {
  555. try {
  556. Thread.sleep(300);
  557. } catch (Exception e) {
  558. }
  559. }
  560. // clear the text in the text field to notify user that the file list
  561. // has loaded.
  562. SwingUtilities.invokeLater(new Runnable()
  563. {
  564. public void run()
  565. {
  566. mSourceFileNameField.setText("");
  567. }
  568. });
  569. }
  570. }
  571. }