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                public void actionPerformed(ActionEvent e) {
151                    final TreePath selected = tree.getSelectionPath();
152
153                    DetailAST ast = (DetailAST) selected.getLastPathComponent();
154                    new CodeSelector(ast, editor, lines2position).select();
155
156                    if (tree.isExpanded(selected)) {
157                        tree.collapsePath(selected);
158                    }
159                    else {
160                        tree.expandPath(selected);
161                    }
162                    tree.setSelectionPath(selected);
163                }
164            };
165        final KeyStroke stroke = KeyStroke.getKeyStroke("ENTER");
166        final String command = "expand/collapse";
167        getInputMap().put(stroke, command);
168        getActionMap().put(command, expand);
169    }
170
171    /**
172     * Overridden to message super and forward the method to the tree.
173     * Since the tree is not actually in the component hierarchy it will
174     * never receive this unless we forward it in this manner.
175     */
176    @Override
177    public void updateUI()
178    {
179        super.updateUI();
180        if (tree != null) {
181            tree.updateUI();
182        }
183        // Use the tree's default foreground and background colors in the
184        // table.
185        LookAndFeel.installColorsAndFont(this, "Tree.background",
186                "Tree.foreground", "Tree.font");
187    }
188
189    /* Workaround for BasicTableUI anomaly. Make sure the UI never tries to
190     * paint the editor. The UI currently uses different techniques to
191     * paint the renderers and editors and overriding setBounds() below
192     * is not the right thing to do for an editor. Returning -1 for the
193     * editing row in this case, ensures the editor is never painted.
194     */
195    @Override
196    public int getEditingRow()
197    {
198        final Class<?> editingClass = getColumnClass(editingColumn);
199        return (editingClass == TreeTableModel.class) ? -1 : editingRow;
200    }
201
202    /**
203     * Overridden to pass the new rowHeight to the tree.
204     */
205    @Override
206    public void setRowHeight(int newRowHeight)
207    {
208        super.setRowHeight(newRowHeight);
209        if ((tree != null) && (tree.getRowHeight() != newRowHeight)) {
210            tree.setRowHeight(getRowHeight());
211        }
212    }
213
214    /**
215     * @return the tree that is being shared between the model.
216     */
217    public JTree getTree()
218    {
219        return tree;
220    }
221
222    /**
223     * A TreeCellRenderer that displays a JTree.
224     */
225    class TreeTableCellRenderer extends JTree implements
226            TableCellRenderer
227    {
228        /**
229         *
230         */
231        private static final long serialVersionUID = 4324031590789321581L;
232        /** Last table/tree row asked to renderer. */
233        protected int visibleRow;
234
235        /** creates a new instance */
236        public TreeTableCellRenderer(TreeModel model)
237        {
238            super(model);
239        }
240
241        /**
242         * updateUI is overridden to set the colors of the Tree's renderer
243         * to match that of the table.
244         */
245        @Override
246        public void updateUI()
247        {
248            super.updateUI();
249            // Make the tree's cell renderer use the table's cell selection
250            // colors.
251            final TreeCellRenderer tcr = getCellRenderer();
252            if (tcr instanceof DefaultTreeCellRenderer) {
253                final DefaultTreeCellRenderer dtcr = ((DefaultTreeCellRenderer) tcr);
254                // For 1.1 uncomment this, 1.2 has a bug that will cause an
255                // exception to be thrown if the border selection color is
256                // null.
257                // dtcr.setBorderSelectionColor(null);
258                dtcr.setTextSelectionColor(UIManager.getColor
259                        ("Table.selectionForeground"));
260                dtcr.setBackgroundSelectionColor(UIManager.getColor
261                        ("Table.selectionBackground"));
262            }
263        }
264
265        /**
266         * Sets the row height of the tree, and forwards the row height to
267         * the table.
268         */
269        @Override
270        public void setRowHeight(int newRowHeight)
271        {
272            if (newRowHeight > 0) {
273                super.setRowHeight(newRowHeight);
274                if ((JTreeTable.this != null) &&
275                    (JTreeTable.this.getRowHeight() != newRowHeight))
276                {
277                    JTreeTable.this.setRowHeight(getRowHeight());
278                }
279            }
280        }
281
282        /**
283         * This is overridden to set the height to match that of the JTable.
284         */
285        @Override
286        public void setBounds(int x, int y, int w, int h)
287        {
288            super.setBounds(x, 0, w, JTreeTable.this.getHeight());
289        }
290
291        /**
292         * Sublcassed to translate the graphics such that the last visible
293         * row will be drawn at 0,0.
294         */
295        @Override
296        public void paint(Graphics g)
297        {
298            g.translate(0, -visibleRow * getRowHeight());
299            super.paint(g);
300        }
301
302        /**
303         * TreeCellRenderer method. Overridden to update the visible row.
304         * @see TableCellRenderer
305         */
306        public Component getTableCellRendererComponent(JTable table,
307                Object value,
308                boolean isSelected,
309                boolean hasFocus,
310                int row, int column)
311        {
312            if (isSelected) {
313                setBackground(table.getSelectionBackground());
314            } else {
315                setBackground(table.getBackground());
316            }
317
318            visibleRow = row;
319            return this;
320        }
321    }
322
323
324    /**
325     * TreeTableCellEditor implementation. Component returned is the
326     * JTree.
327     */
328    public class TreeTableCellEditor extends AbstractCellEditor implements
329            TableCellEditor
330    {
331        public Component getTableCellEditorComponent(JTable table,
332                Object value,
333                boolean isSelected,
334                int r, int c)
335        {
336            return tree;
337        }
338
339        /**
340         * Overridden to return false, and if the event is a mouse event
341         * it is forwarded to the tree.<p>
342         * The behavior for this is debatable, and should really be offered
343         * as a property. By returning false, all keyboard actions are
344         * implemented in terms of the table. By returning true, the
345         * tree would get a chance to do something with the keyboard
346         * events. For the most part this is ok. But for certain keys,
347         * such as left/right, the tree will expand/collapse where as
348         * the table focus should really move to a different column. Page
349         * up/down should also be implemented in terms of the table.
350         * By returning false this also has the added benefit that clicking
351         * outside of the bounds of the tree node, but still in the tree
352         * column will select the row, whereas if this returned true
353         * that wouldn't be the case.
354         * <p>By returning false we are also enforcing the policy that
355         * the tree will never be editable (at least by a key sequence).
356         *
357         * @see TableCellEditor
358         */
359        @Override
360        public boolean isCellEditable(EventObject e)
361        {
362            if (e instanceof MouseEvent) {
363                for (int counter = getColumnCount() - 1; counter >= 0;
364                     counter--) {
365                    if (getColumnClass(counter) == TreeTableModel.class) {
366                        final MouseEvent me = (MouseEvent) e;
367                        final MouseEvent newME = new MouseEvent(tree, me.getID(),
368                                me.getWhen(), me.getModifiers(),
369                                me.getX() - getCellRect(0, counter, true).x,
370                                me.getY(), me.getClickCount(),
371                                me.isPopupTrigger());
372                        tree.dispatchEvent(newME);
373                        break;
374                    }
375                }
376            }
377
378            return false;
379        }
380    }
381
382
383    /**
384     * ListToTreeSelectionModelWrapper extends DefaultTreeSelectionModel
385     * to listen for changes in the ListSelectionModel it maintains. Once
386     * a change in the ListSelectionModel happens, the paths are updated
387     * in the DefaultTreeSelectionModel.
388     */
389    class ListToTreeSelectionModelWrapper extends DefaultTreeSelectionModel
390    {
391        /**
392         *
393         */
394        private static final long serialVersionUID = 2267930983939339510L;
395        /** Set to true when we are updating the ListSelectionModel. */
396        protected boolean updatingListSelectionModel;
397
398        public ListToTreeSelectionModelWrapper()
399        {
400            super();
401            getListSelectionModel().addListSelectionListener
402                    (createListSelectionListener());
403        }
404
405        /**
406         * Returns the list selection model. ListToTreeSelectionModelWrapper
407         * listens for changes to this model and updates the selected paths
408         * accordingly.
409         *
410         * @return the list selection model
411         */
412        ListSelectionModel getListSelectionModel()
413        {
414            return listSelectionModel;
415        }
416
417        /**
418         * This is overridden to set <code>updatingListSelectionModel</code>
419         * and message super. This is the only place DefaultTreeSelectionModel
420         * alters the ListSelectionModel.
421         */
422        @Override
423        public void resetRowSelection()
424        {
425            if (!updatingListSelectionModel) {
426                updatingListSelectionModel = true;
427                try {
428                    super.resetRowSelection();
429                } finally {
430                    updatingListSelectionModel = false;
431                }
432            }
433            // Notice how we don't message super if
434            // updatingListSelectionModel is true. If
435            // updatingListSelectionModel is true, it implies the
436            // ListSelectionModel has already been updated and the
437            // paths are the only thing that needs to be updated.
438        }
439
440        /**
441         * Creates and returns an instance of ListSelectionHandler.
442         */
443        private ListSelectionListener createListSelectionListener()
444        {
445            return new ListSelectionHandler();
446        }
447
448        /**
449         * If <code>updatingListSelectionModel</code> is false, this will
450         * reset the selected paths from the selected rows in the list
451         * selection model.
452         */
453        protected void updateSelectedPathsFromSelectedRows()
454        {
455            if (!updatingListSelectionModel) {
456                updatingListSelectionModel = true;
457                try {
458                    // This is way expensive, ListSelectionModel needs an
459                    // enumerator for iterating.
460                    final int min = listSelectionModel.getMinSelectionIndex();
461                    final int max = listSelectionModel.getMaxSelectionIndex();
462
463                    clearSelection();
464                    if ((min != -1) && (max != -1)) {
465                        for (int counter = min; counter <= max; counter++) {
466                            if (listSelectionModel.isSelectedIndex(counter)) {
467                                final TreePath selPath = tree.getPathForRow
468                                        (counter);
469
470                                if (selPath != null) {
471                                    addSelectionPath(selPath);
472                                }
473                            }
474                        }
475                    }
476                } finally {
477                    updatingListSelectionModel = false;
478                }
479            }
480        }
481
482        /**
483         * Class responsible for calling updateSelectedPathsFromSelectedRows
484         * when the selection of the list changse.
485         */
486        class ListSelectionHandler implements ListSelectionListener
487        {
488            public void valueChanged(ListSelectionEvent e)
489            {
490                updateSelectedPathsFromSelectedRows();
491            }
492        }
493    }
494
495    public void setEditor(JTextArea mJTextArea)
496    {
497         this.editor = mJTextArea;
498    }
499
500    public void setLinePositionMap(List<Integer> lines2position)
501    {
502        this.lines2position = lines2position;
503    }
504}