PageRenderTime 51ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/bundles/plugins-trunk/RubyPlugin/src/org/jedit/ruby/ri/RDocViewer.java

#
Java | 425 lines | 341 code | 62 blank | 22 comment | 66 complexity | ef2579f6d9843d71aead8f208d16a4e8 MD5 | raw file
Possible License(s): BSD-3-Clause, AGPL-1.0, Apache-2.0, LGPL-2.0, LGPL-3.0, GPL-2.0, CC-BY-SA-3.0, LGPL-2.1, GPL-3.0, MPL-2.0-no-copyleft-exception, IPL-1.0
  1. /*
  2. * RDocViewer.java -
  3. *
  4. * Copyright 2005 Robert McKinnon
  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 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.jedit.ruby.ri;
  21. import org.gjt.sp.jedit.View;
  22. import org.gjt.sp.jedit.gui.DefaultFocusComponent;
  23. import org.gjt.sp.jedit.gui.DockableWindowManager;
  24. import org.gjt.sp.jedit.jEdit;
  25. import org.jedit.ruby.cache.RubyCache;
  26. import org.jedit.ruby.ast.*;
  27. import org.jedit.ruby.RubyPlugin;
  28. import javax.swing.*;
  29. import javax.swing.event.*;
  30. import javax.swing.text.html.HTMLEditorKit;
  31. import java.awt.*;
  32. import java.awt.event.FocusAdapter;
  33. import java.awt.event.FocusEvent;
  34. import java.awt.event.ActionListener;
  35. import java.awt.event.ActionEvent;
  36. import java.util.*;
  37. import java.util.List;
  38. /**
  39. * @author robmckinnon at users.sourceforge.net
  40. */
  41. public final class RDocViewer extends JPanel
  42. implements DefaultFocusComponent, ListSelectionListener, DocumentListener {
  43. public static final String EXCLUDE_RAILS = "ruby.rdoc-viewer.exclude-rails";
  44. public static final String INCLUDE_RAILS = "ruby.rdoc-viewer.include-rails";
  45. public static final String INCLUDE_RAILS_2_0 = "ruby.rdoc-viewer.include-rails-2_0";
  46. private static final int MAX_MISMATCHED_CHARACTERS = 3;
  47. private static final Map<RDocViewer, RDocViewer> viewers = new WeakHashMap<RDocViewer, RDocViewer>();
  48. private final View view;
  49. private final JTextField searchField;
  50. private final JList resultList;
  51. private final JTextPane documentationPane;
  52. private int mismatchCharacters;
  53. private final RDocViewerKeyHandler keyHandler;
  54. private final JScrollPane documentationScrollPane;
  55. public RDocViewer(View view, String position) {
  56. super(new BorderLayout());
  57. this.view = view;
  58. mismatchCharacters = 0;
  59. keyHandler = new RDocViewerKeyHandler(this);
  60. searchField = initSearchField(keyHandler);
  61. resultList = initResultList();
  62. documentationPane = initDocumentationPane();
  63. JPanel searchPanel = initSearchPanel(searchField, resultList);
  64. documentationScrollPane = wrapInScrollPane(documentationPane);
  65. add(initSplitPane(position, searchPanel, documentationScrollPane));
  66. add(initRailsPanel(), BorderLayout.SOUTH);
  67. viewers.put(this, null);
  68. setListData(RubyCache.instance().getAllImmediateMembers());
  69. }
  70. private JPanel initRailsPanel() {
  71. JPanel panel = new JPanel(new GridLayout(2,2));
  72. ButtonGroup buttonGroup = new ButtonGroup();
  73. populateRadioButton(INCLUDE_RAILS, buttonGroup, panel, true);
  74. populateRadioButton(EXCLUDE_RAILS, buttonGroup, panel, false);
  75. populateRadioButton(INCLUDE_RAILS_2_0, buttonGroup, panel, false);
  76. return panel;
  77. }
  78. private void populateRadioButton(String actionCommand, final ButtonGroup buttonGroup, JPanel panel, boolean defaultSelected) {
  79. String label = actionCommand+".label";
  80. boolean selected = jEdit.getBooleanProperty(actionCommand, defaultSelected);
  81. JRadioButton radio = new JRadioButton(jEdit.getProperty(label));
  82. radio.setActionCommand(actionCommand);
  83. radio.addActionListener(new ActionListener() {
  84. public void actionPerformed(ActionEvent e) {
  85. try {
  86. view.showWaitCursor();
  87. Enumeration<AbstractButton> buttons = buttonGroup.getElements();
  88. while (buttons.hasMoreElements()) {
  89. AbstractButton button = buttons.nextElement();
  90. jEdit.setBooleanProperty(button.getActionCommand(), button.isSelected());
  91. }
  92. RiParser.parseRdoc();
  93. List<Member> members = RubyCache.instance().getAllImmediateMembers();
  94. setListData(members);
  95. } finally {
  96. view.hideWaitCursor();
  97. documentationPane.setText(jEdit.getProperty(""));
  98. }
  99. }
  100. });
  101. panel.add(radio);
  102. buttonGroup.add(radio);
  103. buttonGroup.setSelected(radio.getModel(), selected);
  104. }
  105. public static void setMemberInViewer(Member member) {
  106. for (RDocViewer viewer : viewers.keySet()) {
  107. if (viewer.isVisible()) {
  108. viewer.setMember(member);
  109. }
  110. }
  111. }
  112. private static void log(String message) {
  113. RubyPlugin.log(message, RDocViewer.class);
  114. }
  115. private void setMember(Member member) {
  116. List<Member> members = RubyCache.instance().getAllImmediateMembers();
  117. setListData(members);
  118. resultList.setSelectedValue(member, true);
  119. handleSelection();
  120. }
  121. private void setListData(final List members) {
  122. if (resultList.getModel() == null || resultList.getModel().getSize() != members.size()) {
  123. resultList.setModel (
  124. new AbstractListModel() {
  125. public int getSize() { return members.size(); }
  126. public Object getElementAt(int i) { return members.get(i); }
  127. }
  128. );
  129. resultList.updateUI();
  130. }
  131. }
  132. public final void focusOnDefaultComponent() {
  133. if (!searchField.hasFocus()) {
  134. searchField.requestFocusInWindow();
  135. }
  136. }
  137. public final void valueChanged(ListSelectionEvent e) {
  138. if (!e.getValueIsAdjusting()) {
  139. handleSelection();
  140. }
  141. }
  142. private void handleSelection() {
  143. Member member = (Member)resultList.getSelectedValue();
  144. if (member != null) {
  145. String documentation = member.getDocumentation();
  146. if (documentation.length() == 0) {
  147. documentation = jEdit.getProperty("ruby.rdoc-viewer.no-description.label");
  148. }
  149. documentationPane.setText(documentation);
  150. documentationPane.setCaretPosition(0);
  151. }
  152. }
  153. private JTextField initSearchField(RDocViewerKeyHandler keyHandler) {
  154. final JTextField textField = new JTextField();
  155. textField.addKeyListener(keyHandler);
  156. textField.getDocument().addDocumentListener(this);
  157. configureAppearence(textField);
  158. textField.setCaretColor(textField.getForeground());
  159. return textField;
  160. }
  161. public final void insertUpdate(DocumentEvent e) {
  162. handleSearchTermEntered();
  163. }
  164. public final void removeUpdate(DocumentEvent e) {
  165. handleSearchTermEntered();
  166. }
  167. public final void changedUpdate(DocumentEvent e) {
  168. handleSearchTermEntered();
  169. }
  170. private void handleSearchTermEntered() {
  171. if (searchField != null) {
  172. String text = searchField.getText();
  173. boolean noTerm = text.length() == 0;
  174. final List<Member> members = noTerm ? RubyCache.instance().getAllImmediateMembers() : getMatchingMembers(text);
  175. if (members.size() == 0) {
  176. if(!keyHandler.lastWasBackspace()
  177. && mismatchCharacters < MAX_MISMATCHED_CHARACTERS) {
  178. mismatchCharacters++;
  179. }
  180. searchField.setForeground(Color.red);
  181. } else {
  182. setListData(members);
  183. if (searchField.getForeground() == Color.red) {
  184. searchField.setForeground(resultList.getForeground());
  185. }
  186. }
  187. setSelected(0);
  188. }
  189. }
  190. private List<Member> getMatchingMembers(String text) {
  191. boolean matchLength = false;
  192. if (text.length() > 0) {
  193. matchLength = Character.isSpaceChar(text.charAt(text.length() - 1));
  194. }
  195. text = text.toLowerCase().trim();
  196. int dotIndex = text.indexOf('.');
  197. String instanceMethod = "";
  198. String classMethod = "";
  199. boolean containsDot = dotIndex != -1;
  200. if (containsDot) {
  201. boolean dotAtEnd = (dotIndex == text.length() - 1);
  202. String start = text.substring(0, dotIndex);
  203. String end = dotAtEnd ? "" : text.substring(dotIndex + 1);
  204. instanceMethod = start + '#' + end;
  205. classMethod = start + "::" + end;
  206. }
  207. List<Member> members = new ArrayList<Member>();
  208. if (containsDot) {
  209. populateMatches(instanceMethod, classMethod, matchLength, members);
  210. } else {
  211. populateMatches(text, matchLength, members);
  212. }
  213. return members;
  214. }
  215. private static void populateMatches(String text, boolean matchLength, List<Member> members) {
  216. for (Member member : RubyCache.instance().getAllImmediateMembers()) {
  217. if (isMatch(member.getFullName(), text, matchLength)) {
  218. members.add(member);
  219. } else if (isMatch(member.getName(), text, matchLength)) {
  220. members.add(member);
  221. }
  222. }
  223. }
  224. private void populateMatches(final String instanceMethod, final String classMethod, final boolean matchLength, final List<Member> members) {
  225. for (Member member : RubyCache.instance().getAllImmediateMembers()) {
  226. member.accept(new MemberVisitorAdapter() {
  227. public void handleMethod(Method method) {
  228. String text = method.isClassMethod() ? classMethod : instanceMethod;
  229. if (isMatch(method.getFullName(), text, matchLength)) {
  230. members.add(method);
  231. }
  232. }
  233. });
  234. }
  235. }
  236. private static boolean isMatch(String memberName, String text, boolean matchLength) {
  237. boolean matched = false;
  238. if (memberName.toLowerCase().startsWith(text)) {
  239. if (!matchLength) {
  240. matched = true;
  241. } else if (memberName.length() == text.length()) {
  242. matched = true;
  243. }
  244. }
  245. return matched;
  246. }
  247. private JList initResultList() {
  248. JList list = new JList();
  249. list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
  250. list.addListSelectionListener(this);
  251. list.addFocusListener(new FocusAdapter() {
  252. public void focusGained(FocusEvent e) {
  253. searchField.requestFocusInWindow();
  254. }
  255. });
  256. configureAppearence(list);
  257. return list;
  258. }
  259. private JTextPane initDocumentationPane() {
  260. HTMLEditorKit kit = new HTMLEditorKit();
  261. kit.setStyleSheet(new RDocStyleSheet(view));
  262. JTextPane pane = new JTextPane();
  263. pane.setEditable(false);
  264. pane.setEditorKit(kit);
  265. configureAppearence(pane);
  266. return pane;
  267. }
  268. private void configureAppearence(JComponent component) {
  269. component.setFont(jEdit.getFontProperty("view.font"));
  270. component.setBackground(jEdit.getColorProperty("view.bgColor"));
  271. component.setForeground(jEdit.getColorProperty("view.fgColor"));
  272. }
  273. private static JScrollPane wrapInScrollPane(JComponent component) {
  274. return new JScrollPane(component, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
  275. }
  276. private JPanel initSearchPanel(JTextField searchField, JList resultList) {
  277. JPanel panel = new JPanel(new BorderLayout());
  278. panel.add(searchField, BorderLayout.NORTH);
  279. panel.add(wrapInScrollPane(resultList));
  280. Dimension size = panel.getPreferredSize();
  281. size.width = 200;
  282. panel.setPreferredSize(size);
  283. panel.setMinimumSize(size);
  284. return panel;
  285. }
  286. private static JSplitPane initSplitPane(String position, JPanel searchPanel, JScrollPane documentationPanel) {
  287. boolean useHorizontalLayout = position.equals(DockableWindowManager.TOP)
  288. || position.equals(DockableWindowManager.BOTTOM)
  289. || position.equals(DockableWindowManager.FLOATING);
  290. if (useHorizontalLayout) {
  291. return new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, searchPanel, documentationPanel);
  292. } else {
  293. return new JSplitPane(JSplitPane.VERTICAL_SPLIT, searchPanel, documentationPanel);
  294. }
  295. }
  296. public final void handleBackSpacePressed() {
  297. if (mismatchCharacters > 0) {
  298. mismatchCharacters--;
  299. }
  300. }
  301. public final int getListSize() {
  302. return resultList.getModel().getSize();
  303. }
  304. public final void incrementSelection(int increment) {
  305. int index = resultList.getSelectionModel().getLeadSelectionIndex();
  306. int size = resultList.getModel().getSize();
  307. index += increment;
  308. index = index < 0 ? 0 : index;
  309. index = index >= size ? size - 1 : index;
  310. setSelected(index);
  311. }
  312. private void setSelected(int index) {
  313. Object item = resultList.getModel().getElementAt(index);
  314. resultList.setSelectedValue(item, true);
  315. }
  316. public final void handleEscapePressed() {
  317. view.getTextArea().requestFocus();
  318. }
  319. public final boolean consumeKeyEvent(char typed) {
  320. String selectedChars = searchField.getSelectedText();
  321. if (mismatchCharacters == MAX_MISMATCHED_CHARACTERS) {
  322. if (selectedChars != null && selectedChars.length() >= mismatchCharacters) {
  323. mismatchCharacters -= (selectedChars.length());
  324. mismatchCharacters = mismatchCharacters < 0 ? 0 : mismatchCharacters;
  325. return false;
  326. } else {
  327. return true;
  328. }
  329. } else if (selectedChars != null && selectedChars.endsWith(" ") && typed == ' ') {
  330. return true;
  331. } else if (typed == '\t') {
  332. documentationPane.requestFocusInWindow();
  333. return true;
  334. } else {
  335. return false;
  336. }
  337. }
  338. private int getBlockIncrement() {
  339. return documentationPane.getVisibleRect().height;
  340. }
  341. public final void pageDown() {
  342. moveScrollBar(getBlockIncrement());
  343. }
  344. public final void pageUp() {
  345. moveScrollBar(-1 * getBlockIncrement());
  346. }
  347. public final void home() {
  348. moveScrollBar(Integer.MIN_VALUE);
  349. }
  350. public final void end() {
  351. moveScrollBar(Integer.MAX_VALUE);
  352. }
  353. private void moveScrollBar(int blockIncrement) {
  354. JScrollBar scrollBar = documentationScrollPane.getVerticalScrollBar();
  355. if (scrollBar != null) {
  356. if (blockIncrement == Integer.MAX_VALUE) {
  357. scrollBar.setValue(scrollBar.getMaximum());
  358. } else {
  359. scrollBar.setValue(scrollBar.getValue() + blockIncrement);
  360. }
  361. }
  362. }
  363. }