    001    /*
    002     * SideKickCompletionPopup.java - Completer popup
    003     * :tabSize=8:indentSize=8:noTabs=false:
    004     * :folding=explicit:collapseFolds=1:
    005     *
    006     * Copyright 2000, 2005 Slava Pestov
    007     *                 2005 Robert McKinnon
    008     *
    009     * The XML plugin is licensed under the GNU General Public License, with
    010     * the following exception:
    011     *
    012     * "Permission is granted to link this code with software released under
    013     * the Apache license version 1.1, for example used by the Xerces XML
    014     * parser package."
    015     */
    016
    017    package sidekick;
    018
    019    //{{{ Imports
    020    import javax.swing.event.*;
    021    import javax.swing.*;
    022    import java.awt.event.*;
    023    import java.awt.*;
    024    import org.gjt.sp.jedit.gui.KeyEventWorkaround;
    025    import org.gjt.sp.jedit.textarea.JEditTextArea;
    026    import org.gjt.sp.jedit.*;
    027    //}}}
    028
    029    public class SideKickCompletionPopup extends JWindow
    030    {
    031            //{{{ fitInScreen() method
    032            public static Point fitInScreen(Point p, Window w, int lineHeight)
    033            {
    034                    Rectangle screenSize = w.getGraphicsConfiguration().getBounds();
    035                    if(p.y + w.getHeight() >= screenSize.height)
    036                            p.y = p.y - w.getHeight() - lineHeight;
    037                    return p;
    038            } //}}}
    039
    040            //{{{ SideKickCompletionPopup constructor
    041            public SideKickCompletionPopup(View view, SideKickParser parser,
    042                    int caret, SideKickCompletion complete)
    043            {
    044                    super(view);
    045
    046                    this.view = view;
    047                    this.parser = parser;
    048                    this.textArea = view.getTextArea();
    049
    050                    list = new JList();
    051
    052                    list.addMouseListener(new MouseHandler());
    053                    list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
    054
    055                    // stupid scrollbar policy is an attempt to work around
    056                    // bugs people have been seeing with IBM's JDK -- 7 Sep 2000
    057                    JScrollPane scroller = new JScrollPane(list,
    058                            JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
    059                            JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
    060
    061                    getContentPane().add(scroller, BorderLayout.CENTER);
    062
    063                    KeyHandler keyHandler = new KeyHandler();
    064                    addKeyListener(keyHandler);
    065                    getRootPane().addKeyListener(keyHandler);
    066                    list.addKeyListener(keyHandler);
    067                    list.addListSelectionListener(new ListHandler());
    068                    view.setKeyEventInterceptor(keyHandler);
    069
    070                    GUIUtilities.requestFocus(this,list);
    071
    072                    this.complete = complete;
    073                    updateListModel();
    074
    075                    Point location = textArea.offsetToXY(caret - complete.getTokenLength());
    076                    location.y += textArea.getPainter().getFontMetrics().getHeight();
    077
    078                    SwingUtilities.convertPointToScreen(location,
    079                            textArea.getPainter());
    080
    081                    setLocation(fitInScreen(location,this,
    082                            textArea.getPainter().getFontMetrics()
    083                            .getHeight()));
    084
    085                    handleFocusOnDispose = true;
    086                    textAreaFocusListener = new FocusAdapter() {
    087                            public void focusLost(FocusEvent e) {
    088                                    handleFocusOnDispose = false;
    089                                    dispose();
    090                            }
    091                    };
    092
    093                    setVisible(true);
    094                    SwingUtilities.invokeLater(new Runnable()
    095                    {
    096                            public void run()
    097                            {
    098                                    textArea.requestFocus(); // fix for focus problems under windows - Rob
    099                    textArea.addFocusListener(textAreaFocusListener);
    100                            }
    101                    });
    102            } //}}}
    103
    104            //{{{ dispose() method
    105            public void dispose()
    106            {
    107                    view.setKeyEventInterceptor(null);
    108                    textArea.removeFocusListener(textAreaFocusListener);
    109                    super.dispose();
    110                    if (handleFocusOnDispose) {
    111                            SwingUtilities.invokeLater(new Runnable()
    112                            {
    113                                    public void run()
    114                                    {
    115                                            view.getTextArea().requestFocus();
    116                                    }
    117                            });
    118                    }
    119            } //}}}
    120
    121            //{{{ Private members
    122
    123            //{{{ Instance variables
    124            private View view;
    125            private JEditTextArea textArea;
    126            private JList list;
    127            private SideKickParser parser;
    128            private SideKickCompletion complete;
    129            private FocusListener textAreaFocusListener;
    130            private boolean handleFocusOnDispose;
    131            //}}}
    132
    133            //{{{ updateListModel() method
    134            private void updateListModel()
    135            {
    136                    if(complete == null || complete.size() == 0)
    137                    {
    138                            list.setListData(new String[] {
    139                                    jEdit.getProperty("sidekick-complete.none")
    140                            });
    141                            list.setCellRenderer(new DefaultListCellRenderer());
    142                            list.setVisibleRowCount(1);
    143                    }
    144                    else
    145                    {
    146                            setListModel(complete);
    147                            list.setCellRenderer(complete.getRenderer());
    148                            list.setVisibleRowCount(Math.min(8,complete.size()));
    149                            list.setFixedCellHeight(list.getCellBounds(0,0).height);
    150                    }
    151
    152                    pack();
    153            } //}}}
    154
    155            //{{{ setListModel() method
    156            private void setListModel(final SideKickCompletion items)
    157            {
    158                    ListModel model = new ListModel()
    159                    {
    160                            public int getSize()
    161                            {
    162                                    return items.size();
    163                            }
    164
    165                            public Object getElementAt(int index)
    166                            {
    167                                    return items.get(index);
    168                            }
    169
    170                            public void addListDataListener(ListDataListener l) {}
    171                            public void removeListDataListener(ListDataListener l) {}
    172                    };
    173
    174                    list.setModel(model);
    175                    list.setSelectedIndex(0);
    176            } //}}}
    177
    178            //{{{ updateSelection() method
    179            void updateSelection()
    180            {
    181                    int index = list.getSelectedIndex();
    182                    if(index == -1)
    183                            return;
    184
    185                    String description = complete.getCompletionDescription(index);
    186                    view.getStatus().setMessageAndClear(description);
    187            } //}}}
    188
    189            //}}}
    190
    191            //{{{ KeyHandler class
    192            class KeyHandler extends KeyAdapter
    193            {
    194                    //{{{ keyPressed() method
    195                    public void keyPressed(KeyEvent evt)
    196                    {
    197                            evt = KeyEventWorkaround.processKeyEvent(evt);
    198                            if(evt == null)
    199                                    return;
    200
    201                            switch(evt.getKeyCode())
    202                            {
    203                            case KeyEvent.VK_ENTER:
    204                                    keyTyped('\n');
    205                                    evt.consume();
    206                                    break;
    207                            case KeyEvent.VK_TAB:
    208                                    keyTyped('\t');
    209                                    evt.consume();
    210                                    break;
    211                            case KeyEvent.VK_ESCAPE:
    212                                    dispose();
    213                                    evt.consume();
    214                                    break;
    215                            case KeyEvent.VK_UP:
    216                                    int selected = list.getSelectedIndex();
    217
    218                                    if(selected == 0)
    219                                            selected = list.getModel().getSize() - 1;
    220                                    else if(getFocusOwner() == list)
    221                                            return;
    222                                    else
    223                                            selected = selected - 1;
    224
    225                                    list.setSelectedIndex(selected);
    226                                    list.ensureIndexIsVisible(selected);
    227
    228                                    evt.consume();
    229                                    break;
    230                            case KeyEvent.VK_DOWN:
    231                                    /* int */ selected = list.getSelectedIndex();
    232
    233                                    if(selected == list.getModel().getSize() - 1)
    234                                            selected = 0;
    235                                    else if(getFocusOwner() == list)
    236                                            return;
    237                                    else
    238                                            selected = selected + 1;
    239
    240                                    list.setSelectedIndex(selected);
    241                                    list.ensureIndexIsVisible(selected);
    242
    243                                    evt.consume();
    244                                    break;
    245                            case KeyEvent.VK_SPACE:
    246                                    break;
    247                            case KeyEvent.VK_BACK_SPACE:
    248                                     if(parser.canHandleBackspace())
    249                                     {
    250                                             keyPressedDefault(evt);
    251                                     }
    252                                     else {
    253                                             dispose();
    254                                             view.processKeyEvent(evt,true);
    255                                     }
    256                                     break;
    257                            case KeyEvent.VK_DELETE:
    258                                    dispose();
    259                                    view.processKeyEvent(evt,true);
    260                                    break;
    261                            default:
    262                                    keyPressedDefault(evt);
    263                                    break;
    264                            }
    265                    } //}}}
    266
    267                    private void keyPressedDefault(KeyEvent evt) {
    268                            // from DefaultInputHandler
    269                            if(!(evt.isControlDown() || evt.isAltDown() || evt.isMetaDown()))
    270                            {
    271                                    if(!evt.isActionKey())
    272                                    {
    273                                            return;
    274                                    }
    275                            }
    276
    277                            dispose();
    278                            view.processKeyEvent(evt,true);
    279                            return;
    280                    }
    281
    282                    //{{{ keyTyped() method
    283                    public void keyTyped(KeyEvent evt)
    284                    {
    285                            evt = KeyEventWorkaround.processKeyEvent(evt);
    286                            if(evt == null)
    287                                    return;
    288
    289                            char ch = evt.getKeyChar();
    290                            if(ch == '\b' && !parser.canHandleBackspace())
    291                                    return;
    292
    293                            keyTyped(ch);
    294
    295                            evt.consume();
    296                    } //}}}
    297
    298                    //{{{ keyTyped() method
    299                    private void keyTyped(char ch)
    300                    {
    301                            if(complete == null)
    302                            {
    303                                    view.getTextArea().userInput(ch);
    304                                    dispose();
    305                            }
    306                            else if(complete.handleKeystroke(list.getSelectedIndex(), ch))
    307                            {
    308                                    EditPane editPane = view.getEditPane();
    309                                    int caret = editPane.getTextArea().getCaretPosition();
    310                                    if(!complete.updateInPlace(editPane, caret))
    311                                    {
    312                                            complete = parser.complete(editPane, caret);
    313                                    }
    314                                    updateListModel();
    315                            }
    316                            else {
    317                                    dispose();
    318                            }
    319                    } //}}}
    320            } //}}}
    321
    322            //{{{ ListHandler class
    323            class ListHandler implements ListSelectionListener
    324            {
    325                    public void valueChanged(ListSelectionEvent evt)
    326                    {
    327                            updateSelection();
    328                    }
    329            } //}}}
    330
    331            //{{{ MouseHandler class
    332            class MouseHandler extends MouseAdapter
    333            {
    334                    public void mouseClicked(MouseEvent evt)
    335                    {
    336                            // XXX
    337                            if(complete != null)
    338                                    complete.handleKeystroke(list.getSelectedIndex(),'\n');
    339                            dispose();
    340                    }
    341            } //}}}
    342    }