PageRenderTime 83ms CodeModel.GetById 39ms app.highlight 39ms RepoModel.GetById 0ms app.codeStats 1ms

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