1/*
2 * Copyright (c) 2001, 2002, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 *
23 */
24
25package sun.jvm.hotspot.ui;
26
27import java.awt.*;
28import java.awt.event.*;
29import java.io.*;
30import java.net.*;
31import java.util.*;
32import javax.swing.*;
33import javax.swing.text.BadLocationException;
34
35/** Panel supporting loading of and scrolling through source code.
36    Contains convenience routines for implementing the Editor
37    interface. */
38
39public class SourceCodePanel extends JPanel {
40  private JTextArea source;
41  private RowHeader header;
42  private String filename;
43  // Amount of white space between edges, line numbers and icons
44  private static final int LINE_NO_SPACE = 4;
45  // Size of icons in resources directory
46  private static final int ICON_SIZE = 12;
47  // Icons used in panel drawing
48  private static Icon topFrameCurLine;
49  private static Icon lowerFrameCurLine;
50  private static Icon breakpoint;
51  // State
52  private int highlightedLine = -1;
53  private Set/*<Integer>*/ breakpoints = new HashSet(); // Zero-based lines internally
54  // Parent Editor container and EditorCommands object for setting breakpoints
55  private EditorCommands comm;
56  private Editor parent;
57
58  /** Support for displaying icons and line numbers in row header of
59      scroll pane */
60  class RowHeader extends JPanel {
61    private JViewport view;
62    private boolean   showLineNumbers;
63    private int       width;
64    private int       rowHeight;
65    private boolean   initted;
66
67    public RowHeader() {
68      super();
69      initted = true;
70      addHierarchyBoundsListener(new HierarchyBoundsAdapter() {
71          public void ancestorResized(HierarchyEvent e) {
72            recomputeSize();
73          }
74        });
75    }
76
77    public void paint(Graphics g) {
78      super.paint(g);
79      if (getShowLineNumbers()) {
80        // Visible region of header panel, in coordinate system of the
81        // panel, is provided by clip bounds of Graphics object. This
82        // is used to figure out which line numbers to draw.
83        Rectangle clip = g.getClipBounds();
84        // To avoid missing lines, round down starting line number and
85        // round up ending line number
86        int start = clip.y / rowHeight;
87        int end   = start + (clip.height + (rowHeight - 1)) / rowHeight;
88        // Draw these line numbers, right justified to look better
89        FontMetrics fm = getFontMetrics(getFont());
90        int ascent = fm.getMaxAscent(); // Causes proper alignment -- trial-and-error
91        for (int i = start; i <= end; i++) {
92          // Line numbers are 1-based
93          String str = Integer.toString(i + 1);
94          int strWidth = GraphicsUtilities.getStringWidth(str, fm);
95          g.drawString(str, width - strWidth - LINE_NO_SPACE, ascent + rowHeight * i);
96
97          // Draw breakpoint if necessary
98          if (breakpoints.contains(new Integer(i))) {
99            breakpoint.paintIcon(this, g, LINE_NO_SPACE, rowHeight * i);
100          }
101
102          // Draw current line icon if necessary
103          if (i == highlightedLine) {
104            // FIXME: use correct icon (not always topmost frame)
105            topFrameCurLine.paintIcon(this, g, LINE_NO_SPACE, rowHeight * i);
106          }
107        }
108      }
109    }
110
111    public boolean getShowLineNumbers() {
112      return showLineNumbers;
113    }
114
115    public void setShowLineNumbers(boolean val) {
116      if (val != showLineNumbers) {
117        showLineNumbers = val;
118        recomputeSize();
119        // Force re-layout
120        invalidate();
121        validate();
122      }
123    }
124
125    public void setFont(Font f) {
126      super.setFont(f);
127      rowHeight = getFontMetrics(f).getHeight();
128      recomputeSize();
129    }
130
131    void setViewport(JViewport view) {
132      this.view = view;
133    }
134
135    void recomputeSize() {
136      if (!initted) return;
137      if (view == null) return;
138      width = ICON_SIZE + 2 * LINE_NO_SPACE;
139      try {
140        int numLines = 1 + source.getLineOfOffset(source.getDocument().getEndPosition().getOffset() - 1);
141        String str = Integer.toString(numLines);
142        if (getShowLineNumbers()) {
143          // Compute width based on whether we are drawing line numbers
144          width += GraphicsUtilities.getStringWidth(str, getFontMetrics(getFont())) + LINE_NO_SPACE;
145        }
146        // FIXME: add on width for all icons (breakpoint, current line,
147        // current line in caller frame)
148        Dimension d = new Dimension(width, numLines * getFontMetrics(getFont()).getHeight());
149        setSize(d);
150        setPreferredSize(d);
151      } catch (BadLocationException e) {
152        e.printStackTrace();
153      }
154    }
155  }
156
157  public SourceCodePanel() {
158    maybeLoadIcons();
159
160    // Build user interface
161    setLayout(new BorderLayout());
162    source = new JTextArea();
163    source.setEditable(false);
164    source.getCaret().setVisible(true);
165    header = new RowHeader();
166    header.setShowLineNumbers(true);
167    JScrollPane scroller = new JScrollPane(source);
168    JViewport rowView = new JViewport();
169    rowView.setView(header);
170    header.setViewport(rowView);
171    rowView.setScrollMode(JViewport.SIMPLE_SCROLL_MODE);
172    scroller.setRowHeader(rowView);
173    add(scroller, BorderLayout.CENTER);
174    // Reset font now that header and source are present
175    setFont(getFont());
176
177    source.addFocusListener(new FocusAdapter() {
178        public void focusGained(FocusEvent e) {
179          source.getCaret().setVisible(true);
180        }
181      });
182
183    source.addKeyListener(new KeyAdapter() {
184        public void keyPressed(KeyEvent e) {
185          if (e.getKeyCode() == KeyEvent.VK_F9) {
186            int lineNo = getCurrentLineNumber();
187            // Only the debugger can figure out whether we are setting
188            // or clearing a breakpoint, since it has the debug
189            // information available and knows whether we're on a
190            // valid line
191            comm.toggleBreakpointAtLine(parent, lineNo);
192          }
193        }
194      });
195
196  }
197
198  public void setFont(Font f) {
199    super.setFont(f);
200    if (source != null) {
201      source.setFont(f);
202    }
203    if (header != null) {
204      header.setFont(f);
205    }
206  }
207
208  public boolean getShowLineNumbers() {
209    return header.getShowLineNumbers();
210  }
211
212  public void setShowLineNumbers(boolean val) {
213    header.setShowLineNumbers(val);
214  }
215
216  public boolean openFile(String filename) {
217    try {
218      this.filename = filename;
219      File file = new File(filename);
220      int len = (int) file.length();
221      StringBuffer buf = new StringBuffer(len); // Approximation
222      char[] tmp = new char[4096];
223      FileReader in = new FileReader(file);
224      int res = 0;
225      do {
226        res = in.read(tmp, 0, tmp.length);
227        if (res >= 0) {
228          buf.append(tmp, 0, res);
229        }
230      } while (res != -1);
231      in.close();
232      String text = buf.toString();
233      source.setText(text);
234      header.recomputeSize();
235      return true;
236    } catch (IOException e) {
237      return false;
238    }
239  }
240
241  public String getSourceFileName() {
242    return filename;
243  }
244
245  /** Line number is one-based */
246  public int getCurrentLineNumber() {
247    try {
248      return 1 + source.getLineOfOffset(source.getCaretPosition());
249    } catch (BadLocationException e) {
250      return 0;
251    }
252  }
253
254  /** Line number is one-based */
255  public void showLineNumber(int lineNo) {
256    try {
257      int offset = source.getLineStartOffset(lineNo - 1);
258      Rectangle rect = source.modelToView(offset);
259      if (rect == null) {
260        return;
261      }
262      source.scrollRectToVisible(rect);
263    } catch (BadLocationException e) {
264      e.printStackTrace();
265    }
266  }
267
268  /** Line number is one-based */
269  public void highlightLineNumber(int lineNo) {
270    highlightedLine = lineNo - 1;
271  }
272
273  public void showBreakpointAtLine(int lineNo)  { breakpoints.add(new Integer(lineNo - 1));    repaint(); }
274  public boolean hasBreakpointAtLine(int lineNo){ return breakpoints.contains(new Integer(lineNo - 1));   }
275  public void clearBreakpointAtLine(int lineNo) { breakpoints.remove(new Integer(lineNo - 1)); repaint(); }
276  public void clearBreakpoints()                { breakpoints.clear();                         repaint(); }
277
278  public void setEditorCommands(EditorCommands comm, Editor parent) {
279    this.comm = comm;
280    this.parent = parent;
281  }
282
283  public void requestFocus() {
284    source.requestFocus();
285  }
286
287  //----------------------------------------------------------------------
288  // Internals only below this point
289  //
290
291  private void maybeLoadIcons() {
292    if (topFrameCurLine == null) {
293      topFrameCurLine   = loadIcon("resources/arrow.png");
294      lowerFrameCurLine = loadIcon("resources/triangle.png");
295      breakpoint        = loadIcon("resources/breakpoint.png");
296    }
297  }
298
299  private Icon loadIcon(String which) {
300    URL url = getClass().getResource(which);
301    return new ImageIcon(url);
302  }
303}
304