    001    /*
    002     * SideKickTree.java
    003     * :tabSize=8:indentSize=8:noTabs=false:
    004     * :folding=explicit:collapseFolds=1:
    005     *
    006     * Copyright (C) 2000, 2003 Slava Pestov
    007     *
    008     * This program is free software; you can redistribute it and/or
    009     * modify it under the terms of the GNU General Public License
    010     * as published by the Free Software Foundation; either version 2
    011     * of the License, or any later version.
    012     *
    013     * This program is distributed in the hope that it will be useful,
    014     * but WITHOUT ANY WARRANTY; without even the implied warranty of
    015     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    016     * GNU General Public License for more details.
    017     *
    018     * You should have received a copy of the GNU General Public License
    019     * along with this program; if not, write to the Free Software
    020     * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
    021     */
    022
    023    package sidekick;
    024
    025    //{{{ Imports
    026    import javax.swing.event.*;
    027    import javax.swing.tree.*;
    028    import javax.swing.*;
    029
    030    import java.awt.event.*;
    031    import java.awt.*;
    032    import java.util.ArrayList;
    033    import java.util.Arrays;
    034    import java.util.Enumeration;
    035    import org.gjt.sp.jedit.gui.*;
    036    import org.gjt.sp.jedit.msg.*;
    037    import org.gjt.sp.jedit.textarea.*;
    038    import org.gjt.sp.jedit.*;
    039
    040    //}}}
    041
    042    /**
    043     * The Structure Browser dockable.  One instance is created for each View.
    044     */
    045    public class SideKickTree extends JPanel implements EBComponent,
    046    DefaultFocusComponent
    047    {
    048            //{{{ Private members
    049
    050            //{{{ Instance variables
    051            private RolloverButton parseBtn;
    052            private JComboBox parserCombo;
    053            protected JTree tree;
    054            protected JEditorPane status;
    055            private JPanel topPanel;
    056            private JSplitPane splitter;
    057            private boolean statusShowing = false;
    058            private Buffer lastParsedBuffer = null;
    059            protected boolean treeFollowsCaret;
    060
    061            protected View view;
    062            private Timer caretTimer;
    063
    064            protected SideKickParsedData data;
    065
    066            private int autoExpandTree = 0;
    067            //}}}
    068
    069            //{{{ SideKickTree constructor
    070            public SideKickTree(View view, boolean docked)
    071            {
    072                    super(new BorderLayout());
    073
    074                    this.view = view;
    075
    076                    topPanel = new JPanel(new BorderLayout());
    077
    078                    // create toolbar with parse button
    079                    JToolBar buttonBox = new JToolBar();
    080                    buttonBox.setFloatable(false);
    081
    082                    parseBtn = new RolloverButton(GUIUtilities.loadIcon("Parse.png"));
    083                    parseBtn.setToolTipText(jEdit.getProperty("sidekick-tree.parse"));
    084                    parseBtn.setMargin(new Insets(0,0,0,0));
    085                    parseBtn.setRequestFocusEnabled(false);
    086                    parseBtn.setEnabled(true);
    087                    ActionListener ah = new ActionHandler();
    088                    parseBtn.addActionListener(ah);
    089                    buttonBox.add(parseBtn);
    090
    091
    092                    buttonBox.add(Box.createGlue());
    093
    094                    parserCombo = new JComboBox();
    095                    reloadParserCombo();
    096                    parserCombo.setToolTipText(jEdit.getProperty("sidekick-tree.parsercombo.tooltip"));
    097
    098                    buttonBox.add(parserCombo);
    099                    parserCombo.addActionListener(ah);
    100
    101
    102                    topPanel.add(BorderLayout.NORTH,buttonBox);
    103
    104                    // create a faux model that will do until a real one arrives
    105                    DefaultTreeModel emptyModel = new DefaultTreeModel(
    106                            new DefaultMutableTreeNode(null));
    107                    tree = buildTree(emptyModel);
    108                    tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
    109                    tree.addKeyListener(new KeyHandler());
    110                    if(docked)
    111                            tree.addMouseMotionListener(new MouseHandler());
    112
    113                    // looks bad with the OS X L&F, apparently...
    114                    if(!OperatingSystem.isMacOSLF())
    115                            tree.putClientProperty("JTree.lineStyle", "Angled");
    116
    117                    tree.setVisibleRowCount(10);
    118                    tree.setCellRenderer(new Renderer());
    119
    120                    topPanel.add(BorderLayout.CENTER,new JScrollPane(tree));
    121
    122                    status = new JEditorPane();
    123                    status.setContentType("text/html");
    124                    status.setEditable(false);
    125                    status.setBackground(jEdit.getColorProperty("view.bgColor"));
    126                    status.setForeground(jEdit.getColorProperty("view.fgColor"));
    127                    JScrollPane status_scroller = new JScrollPane(status);
    128
    129                    splitter = new JSplitPane(JSplitPane.VERTICAL_SPLIT, false, topPanel, status_scroller);
    130
    131    //              status_scroller.setMinimumSize(new Dimension(0, 150));
    132                    splitter.setOneTouchExpandable(true);
    133                    splitter.setResizeWeight(1.0f);
    134                    splitter.setDividerLocation(splitter.getSize().height
    135                                 - splitter.getInsets().bottom
    136                                 - splitter.getDividerSize()
    137                                 - status_scroller.getMinimumSize().height);
    138                    //add(splitter);
    139
    140                    propertiesChanged();
    141
    142                    CaretHandler caretListener = new CaretHandler();
    143
    144                    EditPane[] editPanes = view.getEditPanes();
    145                    for(int i = 0; i < editPanes.length; i++)
    146                    {
    147                            editPanes[i].getTextArea().addCaretListener(
    148                                    caretListener);
    149                    }
    150
    151                    update();
    152            } //}}}
    153
    154            //{{{ focusOnDefaultComponent() method
    155            public void focusOnDefaultComponent()
    156            {
    157                    tree.requestFocus();
    158            } //}}}
    159
    160            //{{{ addNotify() method
    161            public void addNotify()
    162            {
    163                    super.addNotify();
    164                    EditBus.addToBus(this);
    165            } //}}}
    166
    167            //{{{ removeNotify() method
    168            public void removeNotify()
    169            {
    170                    super.removeNotify();
    171                    EditBus.removeFromBus(this);
    172            } //}}}
    173
    174            //{{{ handleMessage() method
    175            public void handleMessage(EBMessage msg)
    176            {
    177
    178
    179                    if(msg instanceof EditPaneUpdate)
    180                    {
    181                            EditPaneUpdate epu = (EditPaneUpdate)msg;
    182                            EditPane editPane = epu.getEditPane();
    183
    184                            if(epu.getWhat() == EditPaneUpdate.CREATED)
    185                                    editPane.getTextArea().addCaretListener(new CaretHandler());
    186                    }
    187                    else if(msg instanceof PropertiesChanged)
    188                            propertiesChanged();
    189                    else if(msg instanceof SideKickUpdate)
    190                    {
    191                            if(((SideKickUpdate)msg).getView() == view)
    192                                    update();
    193                    }
    194                    else if (msg instanceof PluginUpdate) {
    195                            // This causes a ClassCircularityError sometimes, with HTMLSideKick.
    196                            //      reloadParserCombo();
    197                    }
    198            } //}}}
    199
    200            public void setStatus(String msg) {
    201                    status.setText(msg);
    202            }
    203
    204            //{{{ update() method
    205            protected void update()
    206            {
    207                    SideKickParser parser =  SideKickPlugin.getParserForBuffer(view.getBuffer());
    208                    if (parser != null) {
    209                            Object item = parserCombo.getSelectedItem();
    210                            if (item != parser.getName()) {
    211                                    parserCombo.setSelectedItem(parser.getName());
    212                            }
    213                    }
    214
    215                    data = SideKickParsedData.getParsedData(view);
    216                    if(parser == null || data == null)
    217                    {
    218                            DefaultMutableTreeNode root = new DefaultMutableTreeNode(view.getBuffer().getName());
    219                            root.insert(new DefaultMutableTreeNode(
    220                                    jEdit.getProperty("sidekick-tree.not-parsed")),0);
    221
    222                            tree.setModel(new DefaultTreeModel(root));
    223                    }
    224                    else
    225                    {
    226                            tree.setModel(data.tree);
    227                            if(treeFollowsCaret)
    228                                    expandTreeAt(view.getTextArea().getCaretPosition());
    229                    }
    230
    231                    if (autoExpandTree == -1)
    232                            expandAll(true);
    233                    else if (autoExpandTree == 0)
    234                            expandAll(false);
    235                    else if (autoExpandTree > 0) {
    236                            tree.expandRow( 0 );
    237                            for (int i = 1; i < autoExpandTree; i++) {
    238                                    for ( int j = tree.getRowCount() - 1; j > 0; j-- )
    239                                        tree.expandRow( j );
    240                            }
    241                    }
    242            } //}}}
    243
    244            //{{{ expandAll() method
    245            /**
    246             * Expand or collapse all nodes in a tree.
    247             * @param tree the tree
    248             * @param expand if true, expand all nodes, if false, collapse all nodes
    249             */
    250            public void expandAll( boolean expand ) {
    251                    TreeNode root = ( TreeNode ) tree.getModel().getRoot();
    252                    expandAll( new TreePath( root ), expand );
    253            }//}}}
    254
    255            //{{{ expandAll() method
    256            // recursive method to traverse children
    257            private void expandAll( TreePath parent, boolean expand ) {
    258                    TreeNode node = ( TreeNode ) parent.getLastPathComponent();
    259                    if ( node.getChildCount() >= 0 ) {
    260                        for ( Enumeration e = node.children(); e.hasMoreElements(); ) {
    261                            TreeNode n = ( TreeNode ) e.nextElement();
    262                            TreePath path = parent.pathByAddingChild( n );
    263                            expandAll( path, expand );
    264                        }
    265                    }
    266
    267                    // expansion or collapse must be done from the bottom up
    268                    if ( expand ) {
    269                        tree.expandPath( parent );
    270                    }
    271                    else {
    272                        tree.collapsePath( parent );
    273                    }
    274            }//}}}
    275
    276
    277            //{{{ buildTree() method
    278            protected JTree buildTree(DefaultTreeModel model)
    279            {
    280                    return new CustomTree(model);
    281            }//}}}
    282
    283            //{{{ buildActionListener() method
    284            /**
    285             * Creates an action listener for the parse button.
    286             */
    287            protected ActionListener buildActionListener()
    288            {
    289                    return new ActionHandler();
    290            }//}}}
    291
    292
    293            //{{{ propertiesChanged() method
    294            private void propertiesChanged()
    295            {
    296                    treeFollowsCaret = jEdit.getBooleanProperty("sidekick-tree.follows-caret");
    297                    autoExpandTree = jEdit.getIntegerProperty("sidekick-tree.auto-expand-tree-depth", 1);
    298                    if (jEdit.getBooleanProperty("options.sidekick.showStatusWindow") ) {
    299                            if (!statusShowing) {
    300                                    remove(topPanel);
    301                                    splitter.setTopComponent(topPanel);
    302                                    add(splitter);
    303                            }
    304                            statusShowing = true;
    305                    }
    306                    else {
    307                            remove(splitter);
    308                            splitter.remove(topPanel);
    309                            add(topPanel);
    310                            statusShowing = false;
    311                    }
    312            } //}}}
    313
    314            void reloadParserCombo() {
    315                    String[] serviceNames = ServiceManager.getServiceNames(SideKickParser.SERVICE);
    316                    Arrays.sort(serviceNames, new MiscUtilities.StringICaseCompare());
    317                    ArrayList al = new ArrayList();
    318                    al.add("");
    319                    al.addAll(Arrays.asList(serviceNames));
    320                    parserCombo.setModel(new DefaultComboBoxModel(al.toArray()));
    321                    SideKickParser currentParser = SideKickPlugin.getParserForBuffer(view.getBuffer());
    322                    if (currentParser != null) {
    323                            parserCombo.setSelectedItem(currentParser.getName());
    324                    }
    325                    else {
    326                            parserCombo.setSelectedItem("");
    327                    }
    328            }
    329
    330            //{{{ expandTreeWithDelay() method
    331            /**
    332             * Expands the tree after a delay.
    333             * The delay timer is restarted each time this method is called.
    334             */
    335            protected void expandTreeWithDelay()
    336            {
    337                    // if keystroke parse timer is running, do nothing
    338                    // if(keystrokeTimer != null && keystrokeTimer.isRunning())
    339                            // return;
    340
    341                    if(caretTimer != null)
    342                            caretTimer.stop();
    343
    344                    caretTimer = new Timer(0,new ActionListener()
    345                    {
    346                            public void actionPerformed(ActionEvent evt)
    347                            {
    348                                    JEditTextArea textArea = view.getTextArea();
    349                                    int caret = textArea.getCaretPosition();
    350                                    Selection s = textArea.getSelectionAtOffset(caret);
    351                                    expandTreeAt(s == null ? caret : s.getStart());
    352                            }
    353                    });
    354
    355                    caretTimer.setInitialDelay(500);
    356                    caretTimer.setRepeats(false);
    357                    caretTimer.start();
    358            } //}}}
    359
    360            //{{{ expandTreeAt() method
    361            protected void expandTreeAt(int dot)
    362            {
    363                    if(data == null)
    364                            return;
    365
    366                    TreePath treePath = data.getTreePathForPosition(dot);
    367                    if(treePath != null)
    368                    {
    369                            tree.expandPath(treePath);
    370                            tree.setSelectionPath(treePath);
    371                            tree.scrollPathToVisible(treePath);
    372                    }
    373            } //}}}
    374
    375            //}}}
    376
    377            //{{{ Inner classes
    378
    379            //{{{ CustomTree class
    380            /**
    381             * A JTree with added mouse handling.  Other plugins providing similar trees
    382             * can extend CustomTree and override the mouse methods.
    383             */
    384            protected class CustomTree extends JTree
    385            {
    386                    protected CustomTree(TreeModel model)
    387                    {
    388                            super(model);
    389                    }
    390
    391                    protected void processMouseEvent(MouseEvent evt)
    392                    {
    393                            switch(evt.getID())
    394                            {
    395                            //{{{ MOUSE_PRESSED...
    396                            case MouseEvent.MOUSE_PRESSED:
    397                                    TreePath path = getPathForLocation(
    398                                            evt.getX(),evt.getY());
    399                                    if(path != null)
    400                                    {
    401                                            Object value = ((DefaultMutableTreeNode)path
    402                                                    .getLastPathComponent()).getUserObject();
    403
    404                                            if(value instanceof IAsset)
    405                                            {
    406                                                    IAsset asset = (IAsset)value;
    407
    408                                                    JEditTextArea textArea = view.getTextArea();
    409
    410                                                    if(evt.getClickCount() == 2)
    411                                                    {
    412                                                            doubleClicked(view,asset,path);
    413                                                    }
    414                                                    else if(evt.isShiftDown())
    415                                                    {
    416                                                            shiftClick(view,asset,path);
    417                                                    }
    418                                                    else if(evt.isControlDown())
    419                                                    {
    420                                                            controlClick(view,asset,path);
    421                                                    }
    422                                                    else
    423                                                            textArea.setCaretPosition(asset.getStart().getOffset());
    424                                            }
    425                                    }
    426
    427                                    super.processMouseEvent(evt);
    428                                    break; //}}}
    429                            //{{{ MOUSE_EXITED...
    430                            case MouseEvent.MOUSE_EXITED:
    431                                    view.getStatus().setMessage(null);
    432                                    super.processMouseEvent(evt);
    433                                    break; //}}}
    434                            default:
    435                                    super.processMouseEvent(evt);
    436                                    break;
    437                            }
    438                    }
    439
    440                    protected void doubleClicked(View view, IAsset asset, TreePath path)
    441                    {
    442                    }
    443
    444                    protected void shiftClick(View view, IAsset asset, TreePath path)
    445                    {
    446                            JEditTextArea textArea = view.getTextArea();
    447                            textArea.setCaretPosition(asset.getEnd().getOffset());
    448                            textArea.addToSelection(
    449                                    new Selection.Range(
    450                                            asset.getStart().getOffset(),
    451                                            asset.getEnd().getOffset()));
    452                    }
    453
    454                    protected void controlClick(View view, IAsset asset, TreePath path)
    455                    {
    456                            JEditTextArea textArea = view.getTextArea();
    457                            textArea.getDisplayManager().narrow(
    458                                    textArea.getLineOfOffset(asset.getStart().getOffset()),
    459                                    textArea.getLineOfOffset(asset.getEnd().getOffset()));
    460                    }
    461            } //}}}
    462
    463            //{{{ ActionHandler class
    464            class ActionHandler implements ActionListener
    465            {
    466                    int level=0;
    467                    public void actionPerformed(ActionEvent evt)
    468                    {
    469                            level++;
    470                            if (level>1) {
    471                                    level--;
    472                                    return;
    473                            }
    474                            Buffer b = view.getBuffer();
    475                            if (evt.getSource() ==  parserCombo ) {
    476
    477                                    Object selectedParser = parserCombo.getSelectedItem();
    478                                    String preferredParser = b.getStringProperty(SideKickPlugin.PARSER_PROPERTY);
    479
    480                                    if (selectedParser.toString().equals(preferredParser)) {
    481                                            b.setProperty("usermode", null);
    482                                    }
    483                                    else {
    484                                            if (selectedParser != null) {
    485                                                    SideKickPlugin.setParserForBuffer(b, selectedParser.toString());
    486                                                    b.setProperty("usermode", Boolean.TRUE);
    487                                            }
    488                                    }
    489                            }
    490                            if (evt.getSource() == parseBtn) {
    491                                    Object usermode =  b.getProperty("usermode");
    492                                    if (usermode == null || usermode == Boolean.FALSE) {
    493                                            SideKickParser sp = SideKickPlugin.getParserForBuffer(b);
    494                                            if (sp == null)  return;
    495                                            else reloadParserCombo();
    496                                    }
    497                            }
    498                            lastParsedBuffer = view.getBuffer();
    499                            SideKickPlugin.parse(view,true);
    500                            level--;
    501                    }
    502            } //}}}
    503
    504            //{{{ CaretHandler class
    505            class CaretHandler implements CaretListener
    506            {
    507                    public void caretUpdate(CaretEvent evt)
    508                    {
    509                            if (view.getBuffer() != lastParsedBuffer) return;
    510                            if(evt.getSource() == view.getTextArea() && treeFollowsCaret) {
    511                                    expandTreeWithDelay();
    512                            }
    513                    }
    514            } //}}}
    515
    516            //{{{ KeyHandler class
    517            class KeyHandler extends KeyAdapter
    518            {
    519                    public void keyPressed(KeyEvent evt)
    520                    {
    521                            if(caretTimer != null)
    522                                    caretTimer.stop();
    523
    524                            if(evt.getKeyCode() == KeyEvent.VK_ENTER)
    525                            {
    526                                    evt.consume();
    527
    528                                    TreePath path = tree.getSelectionPath();
    529
    530                                    if(path != null)
    531                                    {
    532                                            Object value = ((DefaultMutableTreeNode)path
    533                                                    .getLastPathComponent()).getUserObject();
    534
    535                                            if(value instanceof IAsset)
    536                                            {
    537                                                    IAsset asset = (IAsset)value;
    538
    539                                                    JEditTextArea textArea = view.getTextArea();
    540
    541                                                    if(evt.isShiftDown())
    542                                                    {
    543                                                            textArea.setCaretPosition(asset.getEnd().getOffset());
    544                                                            textArea.addToSelection(
    545                                                                    new Selection.Range(
    546                                                                            asset.getStart().getOffset(),
    547                                                                            asset.getEnd().getOffset()));
    548                                                    }
    549                                                    else
    550                                                            textArea.setCaretPosition(asset.getStart().getOffset());
    551                                            }
    552                                    }
    553                            }
    554                    }
    555            } //}}}
    556
    557            //{{{ MouseHandler class
    558            class MouseHandler extends MouseMotionAdapter
    559            {
    560                    public void mouseMoved(MouseEvent evt)
    561                    {
    562                            TreePath path = tree.getPathForLocation(
    563                                    evt.getX(),evt.getY());
    564                            if(path == null)
    565                                    view.getStatus().setMessage(null);
    566                            else
    567                            {
    568                                    Object value = ((DefaultMutableTreeNode)path
    569                                            .getLastPathComponent()).getUserObject();
    570
    571                                    if(value instanceof IAsset)
    572                                    {
    573                                            String info = ((IAsset)value).getShortString();
    574                                            view.getStatus().setMessage(info);
    575                                    }
    576                            }
    577                    }
    578            } //}}}
    579
    580            //{{{ Renderer class
    581            class Renderer extends DefaultTreeCellRenderer
    582            {
    583                    public Component getTreeCellRendererComponent(JTree tree,
    584                            Object value, boolean sel, boolean expanded,
    585                            boolean leaf, int row, boolean hasFocus)
    586                    {
    587                            super.getTreeCellRendererComponent(tree,value,sel,
    588                                    expanded,leaf,row,hasFocus);
    589
    590                            DefaultMutableTreeNode node = (DefaultMutableTreeNode)value;
    591                            Object nodeValue = node.getUserObject();
    592                            if(nodeValue instanceof IAsset)
    593                            {
    594                                    IAsset asset = (IAsset)node.getUserObject();
    595
    596                                    setText(asset.getShortString());
    597                                    setIcon(asset.getIcon());
    598                            }
    599                            // is root?
    600                            else if(node.getParent() == null)
    601                            {
    602                                    setIcon(org.gjt.sp.jedit.browser.FileCellRenderer
    603                                            .fileIcon);
    604                            }
    605                            else
    606                                    setIcon(null);
    607
    608                            return this;
    609                    }
    610            } //}}}
    611
    612            //}}}
    613    }