001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2002  Oliver Burn
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018////////////////////////////////////////////////////////////////////////////////
019
020/*
021 * %W% %E%
022 *
023 * Copyright 1997, 1998 Sun Microsystems, Inc. All Rights Reserved.
024 *
025 * Redistribution and use in source and binary forms, with or
026 * without modification, are permitted provided that the following
027 * conditions are met:
028 *
029 * - Redistributions of source code must retain the above copyright
030 *   notice, this list of conditions and the following disclaimer.
031 *
032 * - Redistribution in binary form must reproduce the above
033 *   copyright notice, this list of conditions and the following
034 *   disclaimer in the documentation and/or other materials
035 *   provided with the distribution.
036 *
037 * Neither the name of Sun Microsystems, Inc. or the names of
038 * contributors may be used to endorse or promote products derived
039 * from this software without specific prior written permission.
040 *
041 * This software is provided "AS IS," without a warranty of any
042 * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
043 * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
044 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
045 * EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY
046 * DAMAGES OR LIABILITIES SUFFERED BY LICENSEE AS A RESULT OF OR
047 * RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THIS SOFTWARE OR
048 * ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE
049 * FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT,
050 * SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
051 * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF
052 * THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS
053 * BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
054 *
055 * You acknowledge that this software is not designed, licensed or
056 * intended for use in the design, construction, operation or
057 * maintenance of any nuclear facility.
058 */
059
060package com.puppycrawl.tools.checkstyle.gui;
061
062import java.awt.Component;
063import java.awt.Dimension;
064import java.awt.Graphics;
065import java.awt.event.ActionEvent;
066import java.awt.event.MouseEvent;
067import java.util.EventObject;
068import java.util.List;
069
070import javax.swing.Action;
071import javax.swing.AbstractAction;
072import javax.swing.JTable;
073import javax.swing.JTextArea;
074import javax.swing.JTree;
075import javax.swing.KeyStroke;
076import javax.swing.ListSelectionModel;
077import javax.swing.LookAndFeel;
078import javax.swing.UIManager;
079import javax.swing.event.ListSelectionEvent;
080import javax.swing.event.ListSelectionListener;
081import javax.swing.table.TableCellEditor;
082import javax.swing.table.TableCellRenderer;
083import javax.swing.tree.DefaultTreeCellRenderer;
084import javax.swing.tree.DefaultTreeSelectionModel;
085import javax.swing.tree.TreeCellRenderer;
086import javax.swing.tree.TreeModel;
087import javax.swing.tree.TreePath;
088
089import com.puppycrawl.tools.checkstyle.api.DetailAST;
090
091/**
092 * This example shows how to create a simple JTreeTable component,
093 * by using a JTree as a renderer (and editor) for the cells in a
094 * particular column in the JTable.
095 *
096 * <a href="http://java.sun.com/products/jfc/tsc/articles/treetable1/index.html">Original&nbsp;Source&nbsp;Location</a>
097 *
098 * @author Philip Milne
099 * @author Scott Violet
100 * @author Lars Kühne
101 */
102public class JTreeTable extends JTable
103{
104    /** For Serialisation that will never happen. */
105    private static final long serialVersionUID = -8493693409423365387L;
106    /** A subclass of JTree. */
107    protected TreeTableCellRenderer tree;
108    private JTextArea editor;
109    private List<Integer> lines2position;
110
111    public JTreeTable(TreeTableModel treeTableModel)
112    {
113        super();
114
115        // Create the tree. It will be used as a renderer and editor.
116        tree = new TreeTableCellRenderer(treeTableModel);
117
118        // Install a tableModel representing the visible rows in the tree.
119        super.setModel(new TreeTableModelAdapter(treeTableModel, tree));
120
121        // Force the JTable and JTree to share their row selection models.
122        final ListToTreeSelectionModelWrapper selectionWrapper = new
123                ListToTreeSelectionModelWrapper();
124        tree.setSelectionModel(selectionWrapper);
125        setSelectionModel(selectionWrapper.getListSelectionModel());
126
127        // Install the tree editor renderer and editor.
128        setDefaultRenderer(TreeTableModel.class, tree);
129        setDefaultEditor(TreeTableModel.class, new TreeTableCellEditor());
130
131        // No grid.
132        setShowGrid(false);
133
134        // No intercell spacing
135        setIntercellSpacing(new Dimension(0, 0));
136
137        // And update the height of the trees row to match that of
138        // the table.
139        if (tree.getRowHeight() < 1) {
140            // Metal looks better like this.
141            setRowHeight(getRowHeight());
142        }
143
144        final Action expand = new AbstractAction() {
145                /**
146             *
147             */
148            private static final long serialVersionUID = -5859674518660156121L;
149
150                @Override
151                public void actionPerformed(ActionEvent e) {
152                    final TreePath selected = tree.getSelectionPath();
153
154                    DetailAST ast = (DetailAST) selected.getLastPathComponent();
155                    new CodeSelector(ast, editor, lines2position).select();
156
157                    if (tree.isExpanded(selected)) {
158                        tree.collapsePath(selected);
159                    }
160                    else {
161                        tree.expandPath(selected);
162                    }
163                    tree.setSelectionPath(selected);
164                }
165            };
166        final KeyStroke stroke = KeyStroke.getKeyStroke("ENTER");
167        final String command = "expand/collapse";
168        getInputMap().put(stroke, command);
169        getActionMap().put(command, expand);
170    }
171
172    /**
173     * Overridden to message super and forward the method to the tree.
174     * Since the tree is not actually in the component hierarchy it will
175     * never receive this unless we forward it in this manner.
176     */
177    @Override
178    public void updateUI()
179    {
180        super.updateUI();
181        if (tree != null) {
182            tree.updateUI();
183        }
184        // Use the tree's default foreground and background colors in the
185        // table.
186        LookAndFeel.installColorsAndFont(this, "Tree.background",
187                "Tree.foreground", "Tree.font");
188    }
189
190    /* Workaround for BasicTableUI anomaly. Make sure the UI never tries to
191     * paint the editor. The UI currently uses different techniques to
192     * paint the renderers and editors and overriding setBounds() below
193     * is not the right thing to do for an editor. Returning -1 for the
194     * editing row in this case, ensures the editor is never painted.
195     */
196    @Override
197    public int getEditingRow()
198    {
199        final Class<?> editingClass = getColumnClass(editingColumn);
200        return (editingClass == TreeTableModel.class) ? -1 : editingRow;
201    }
202
203    /**
204     * Overridden to pass the new rowHeight to the tree.
205     */
206    @Override
207    public void setRowHeight(int newRowHeight)
208    {
209        super.setRowHeight(newRowHeight);
210        if ((tree != null) && (tree.getRowHeight() != newRowHeight)) {
211            tree.setRowHeight(getRowHeight());
212        }
213    }
214
215    /**
216     * @return the tree that is being shared between the model.
217     */
218    public JTree getTree()
219    {
220        return tree;
221    }
222
223    /**
224     * A TreeCellRenderer that displays a JTree.
225     */
226    class TreeTableCellRenderer extends JTree implements
227            TableCellRenderer
228    {
229        /**
230         *
231         */
232        private static final long serialVersionUID = 4324031590789321581L;
233        /** Last table/tree row asked to renderer. */
234        protected int visibleRow;
235
236        /** creates a new instance */
237        public TreeTableCellRenderer(TreeModel model)
238        {
239            super(model);
240        }
241
242        /**
243         * updateUI is overridden to set the colors of the Tree's renderer
244         * to match that of the table.
245         */
246        @Override
247        public void updateUI()
248        {
249            super.updateUI();
250            // Make the tree's cell renderer use the table's cell selection
251            // colors.
252            final TreeCellRenderer tcr = getCellRenderer();
253            if (tcr instanceof DefaultTreeCellRenderer) {
254                final DefaultTreeCellRenderer dtcr = ((DefaultTreeCellRenderer) tcr);
255                // For 1.1 uncomment this, 1.2 has a bug that will cause an
256                // exception to be thrown if the border selection color is
257                // null.
258                // dtcr.setBorderSelectionColor(null);
259                dtcr.setTextSelectionColor(UIManager.getColor
260                        ("Table.selectionForeground"));
261                dtcr.setBackgroundSelectionColor(UIManager.getColor
262                        ("Table.selectionBackground"));
263            }
264        }
265
266        /**
267         * Sets the row height of the tree, and forwards the row height to
268         * the table.
269         */
270        @Override
271        public void setRowHeight(int newRowHeight)
272        {
273            if (newRowHeight > 0) {
274                super.setRowHeight(newRowHeight);
275                if ((JTreeTable.this != null) &&
276                    (JTreeTable.this.getRowHeight() != newRowHeight))
277                {
278                    JTreeTable.this.setRowHeight(getRowHeight());
279                }
280            }
281        }
282
283        /**
284         * This is overridden to set the height to match that of the JTable.
285         */
286        @Override
287        public void setBounds(int x, int y, int w, int h)
288        {
289            super.setBounds(x, 0, w, JTreeTable.this.getHeight());
290        }
291
292        /**
293         * Sublcassed to translate the graphics such that the last visible
294         * row will be drawn at 0,0.
295         */
296        @Override
297        public void paint(Graphics g)
298        {
299            g.translate(0, -visibleRow * getRowHeight());
300            super.paint(g);
301        }
302
303        /**
304         * TreeCellRenderer method. Overridden to update the visible row.
305         * @see TableCellRenderer
306         */
307        @Override
308        public Component getTableCellRendererComponent(JTable table,
309                Object value,
310                boolean isSelected,
311                boolean hasFocus,
312                int row, int column)
313        {
314            if (isSelected) {
315                setBackground(table.getSelectionBackground());
316            } else {
317                setBackground(table.getBackground());
318            }
319
320            visibleRow = row;
321            return this;
322        }
323    }
324
325
326    /**
327     * TreeTableCellEditor implementation. Component returned is the
328     * JTree.
329     */
330    public class TreeTableCellEditor extends AbstractCellEditor implements
331            TableCellEditor
332    {
333        @Override
334        public Component getTableCellEditorComponent(JTable table,
335                Object value,
336                boolean isSelected,
337                int r, int c)
338        {
339            return tree;
340        }
341
342        /**
343         * Overridden to return false, and if the event is a mouse event
344         * it is forwarded to the tree.<p>
345         * The behavior for this is debatable, and should really be offered
346         * as a property. By returning false, all keyboard actions are
347         * implemented in terms of the table. By returning true, the
348         * tree would get a chance to do something with the keyboard
349         * events. For the most part this is ok. But for certain keys,
350         * such as left/right, the tree will expand/collapse where as
351         * the table focus should really move to a different column. Page
352         * up/down should also be implemented in terms of the table.
353         * By returning false this also has the added benefit that clicking
354         * outside of the bounds of the tree node, but still in the tree
355         * column will select the row, whereas if this returned true
356         * that wouldn't be the case.
357         * <p>By returning false we are also enforcing the policy that
358         * the tree will never be editable (at least by a key sequence).
359         *
360         * @see TableCellEditor
361         */
362        @Override
363        public boolean isCellEditable(EventObject e)
364        {
365            if (e instanceof MouseEvent) {
366                for (int counter = getColumnCount() - 1; counter >= 0;
367                     counter--) {
368                    if (getColumnClass(counter) == TreeTableModel.class) {
369                        final MouseEvent me = (MouseEvent) e;
370                        final MouseEvent newME = new MouseEvent(tree, me.getID(),
371                                me.getWhen(), me.getModifiers(),
372                                me.getX() - getCellRect(0, counter, true).x,
373                                me.getY(), me.getClickCount(),
374                                me.isPopupTrigger());
375                        tree.dispatchEvent(newME);
376                        break;
377                    }
378                }
379            }
380
381            return false;
382        }
383    }
384
385
386    /**
387     * ListToTreeSelectionModelWrapper extends DefaultTreeSelectionModel
388     * to listen for changes in the ListSelectionModel it maintains. Once
389     * a change in the ListSelectionModel happens, the paths are updated
390     * in the DefaultTreeSelectionModel.
391     */
392    class ListToTreeSelectionModelWrapper extends DefaultTreeSelectionModel
393    {
394        /**
395         *
396         */
397        private static final long serialVersionUID = 2267930983939339510L;
398        /** Set to true when we are updating the ListSelectionModel. */
399        protected boolean updatingListSelectionModel;
400
401        public ListToTreeSelectionModelWrapper()
402        {
403            super();
404            getListSelectionModel().addListSelectionListener
405                    (createListSelectionListener());
406        }
407
408        /**
409         * Returns the list selection model. ListToTreeSelectionModelWrapper
410         * listens for changes to this model and updates the selected paths
411         * accordingly.
412         *
413         * @return the list selection model
414         */
415        ListSelectionModel getListSelectionModel()
416        {
417            return listSelectionModel;
418        }
419
420        /**
421         * This is overridden to set <code>updatingListSelectionModel</code>
422         * and message super. This is the only place DefaultTreeSelectionModel
423         * alters the ListSelectionModel.
424         */
425        @Override
426        public void resetRowSelection()
427        {
428            if (!updatingListSelectionModel) {
429                updatingListSelectionModel = true;
430                try {
431                    super.resetRowSelection();
432                } finally {
433                    updatingListSelectionModel = false;
434                }
435            }
436            // Notice how we don't message super if
437            // updatingListSelectionModel is true. If
438            // updatingListSelectionModel is true, it implies the
439            // ListSelectionModel has already been updated and the
440            // paths are the only thing that needs to be updated.
441        }
442
443        /**
444         * Creates and returns an instance of ListSelectionHandler.
445         */
446        private ListSelectionListener createListSelectionListener()
447        {
448            return new ListSelectionHandler();
449        }
450
451        /**
452         * If <code>updatingListSelectionModel</code> is false, this will
453         * reset the selected paths from the selected rows in the list
454         * selection model.
455         */
456        protected void updateSelectedPathsFromSelectedRows()
457        {
458            if (!updatingListSelectionModel) {
459                updatingListSelectionModel = true;
460                try {
461                    // This is way expensive, ListSelectionModel needs an
462                    // enumerator for iterating.
463                    final int min = listSelectionModel.getMinSelectionIndex();
464                    final int max = listSelectionModel.getMaxSelectionIndex();
465
466                    clearSelection();
467                    if ((min != -1) && (max != -1)) {
468                        for (int counter = min; counter <= max; counter++) {
469                            if (listSelectionModel.isSelectedIndex(counter)) {
470                                final TreePath selPath = tree.getPathForRow
471                                        (counter);
472
473                                if (selPath != null) {
474                                    addSelectionPath(selPath);
475                                }
476                            }
477                        }
478                    }
479                } finally {
480                    updatingListSelectionModel = false;
481                }
482            }
483        }
484
485        /**
486         * Class responsible for calling updateSelectedPathsFromSelectedRows
487         * when the selection of the list changse.
488         */
489        class ListSelectionHandler implements ListSelectionListener
490        {
491            @Override
492            public void valueChanged(ListSelectionEvent e)
493            {
494                updateSelectedPathsFromSelectedRows();
495            }
496        }
497    }
498
499    public void setEditor(JTextArea mJTextArea)
500    {
501         this.editor = mJTextArea;
502    }
503
504    public void setLinePositionMap(List<Integer> lines2position)
505    {
506        this.lines2position = lines2position;
507    }
508}