View Javadoc

1   package de.matthias_burbach.deputy.swing;
2   
3   import java.awt.BorderLayout;
4   import java.awt.Color;
5   import java.awt.Component;
6   import java.awt.Cursor;
7   import java.awt.event.ActionEvent;
8   import java.awt.event.KeyAdapter;
9   import java.awt.event.KeyEvent;
10  import java.awt.event.MouseAdapter;
11  import java.awt.event.MouseEvent;
12  import java.awt.event.MouseMotionListener;
13  
14  import javax.swing.AbstractAction;
15  import javax.swing.BorderFactory;
16  import javax.swing.JEditorPane;
17  import javax.swing.JPanel;
18  import javax.swing.JPopupMenu;
19  import javax.swing.JScrollPane;
20  import javax.swing.JTextPane;
21  import javax.swing.ScrollPaneConstants;
22  import javax.swing.SwingUtilities;
23  import javax.swing.event.DocumentEvent;
24  import javax.swing.event.DocumentListener;
25  import javax.swing.text.BadLocationException;
26  import javax.swing.text.DefaultStyledDocument;
27  import javax.swing.text.Element;
28  import javax.swing.text.Position;
29  import javax.swing.text.SimpleAttributeSet;
30  import javax.swing.text.StyleConstants;
31  import javax.swing.text.StyledDocument;
32  
33  import de.matthias_burbach.deputy.core.util.Log;
34  
35  /***
36   * Displays log messages to the user.
37   *
38   * @author Matthias Burbach
39   */
40  public class DeputyLogPanel extends JPanel implements Log {
41      /***
42       * The default attributes for displaying text in this log panel.
43       */
44      private SimpleAttributeSet defaultAttributes;
45  
46      /***
47       * The internal document to display the contents of this log panel.
48       */
49      private DefaultStyledDocument document;
50  
51      /***
52       * Currently not in use. Nice feature to display hyperlinks in the log
53       * panel.
54       */
55      public static final String LINK_KEY = "Link";
56  
57      /***
58       * The JTextPane used for displaying the output.
59       */
60      private JTextPane textPane = null;
61  
62      /***
63       * Constructs and initializes the log panel.
64       */
65      public DeputyLogPanel() {
66          /*
67           * Create the default attributes for the document
68           */
69          defaultAttributes = new SimpleAttributeSet();
70          StyleConstants.setBold(defaultAttributes, false);
71          StyleConstants.setFontFamily(defaultAttributes, "Courier");
72          StyleConstants.setForeground(defaultAttributes, Color.BLACK);
73  
74          /*
75           * Create the document for the text pane
76           */
77          document = new DefaultStyledDocument();
78  
79          document.addDocumentListener(new DocumentListener() {
80              public void changedUpdate(final DocumentEvent e) {
81                  // do nothing
82              }
83              public void insertUpdate(final DocumentEvent e) {
84                  textPane.setCaretPosition(document.getLength());
85              }
86              public void removeUpdate(final DocumentEvent e) {
87                  // do nothing
88              }
89          });
90  
91          /*
92           * Create the text pane
93           */
94          textPane = new JTextPane() {
95              public boolean getScrollableTracksViewportWidth() {
96                  Component parent = this.getParent();
97                  int width = this.getUI().getPreferredSize(this).width;
98                  return (width <= parent.getSize().width);
99              }
100         };
101 
102         textPane.addMouseMotionListener(new MouseMotionListener() {
103             public void mouseMoved(final MouseEvent e) {
104                 Element c = characterElementAt(e);
105 
106                 if (c.getAttributes().getAttribute(LINK_KEY) != null) {
107                     textPane.setCursor(
108                         Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
109                 } else {
110                     textPane.setCursor(Cursor.getDefaultCursor());
111                 }
112             }
113             public void mouseDragged(final MouseEvent e) {
114                 //empty
115             }
116         });
117 
118         textPane.addMouseListener(new MouseAdapter() {
119             public void mousePressed(final MouseEvent e) {
120                 Element c = characterElementAt(e);
121                 String url = (String) c.getAttributes().getAttribute(LINK_KEY);
122                 if (url != null) {
123                     fireHyperlink(url);
124                     return;
125                 }
126                 super.mousePressed(e);
127             }
128         });
129 
130         textPane.setEditable(false);
131         textPane.setDocument(document);
132         textPane.addKeyListener(new KeyAdapter() {
133             public void keyReleased(final KeyEvent e) {
134                 e.consume();
135             }
136             public void keyPressed(final KeyEvent e) {
137                 if (e.getModifiers() == 2) {
138                      if (e.getKeyCode() == KeyEvent.VK_A) {
139                         textPane.selectAll();
140                     }
141                 }
142             }
143         });
144 
145         /*
146          * Add a popup menu to the text pane.
147          */
148         final JPopupMenu popupMenu = new JPopupMenu();
149         popupMenu.add(new AbstractAction("Clear Log") {
150             public void actionPerformed(final ActionEvent e) {
151                 clear();
152             }
153         });
154         textPane.add(popupMenu);
155         textPane.addMouseListener(new MouseAdapter() {
156             public void mouseReleased(final MouseEvent e) {
157                 if (e.isPopupTrigger()) {
158                     int x = e.getX();
159                     int y = e.getY();
160                     popupMenu.show(textPane, x, y);
161                 }
162             }
163         });
164 
165         /*
166          * Wrap the text pane in a scroll pane
167          */
168         JScrollPane scrollPane = new JScrollPane(textPane);
169         scrollPane.setVerticalScrollBarPolicy(
170             ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
171         scrollPane.setHorizontalScrollBarPolicy(
172             ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
173 
174         /*
175          * Add the scroll pane to this panel
176          */
177         setBorder(BorderFactory.createTitledBorder("Log"));
178         setLayout(new BorderLayout());
179         add(scrollPane, BorderLayout.CENTER);
180     }
181 
182     /***
183      * @param e The mouse event that fired.
184      * @return The character element under the mouse pointer.
185      */
186     private static Element characterElementAt(final MouseEvent e) {
187         JEditorPane p = (JEditorPane) e.getComponent();
188         Position.Bias[] bias = new Position.Bias[1];
189         int position = p.getUI().viewToModel(p, e.getPoint(), bias);
190 
191         if (bias[0] == Position.Bias.Backward && position != 0) {
192             position--;
193         }
194         Element c =
195             ((StyledDocument) p.getDocument()).getCharacterElement(position);
196         // should test whether really inside
197         return c;
198     }
199 
200 
201     /***
202      * Adds a line of text to the JTextPane using the default SimpleAttributSet.
203      * <br/>
204      * Automatically adds a newline at the end of the string.
205      *
206      * @param s The line to add.
207      */
208     public void addLine(final String s) {
209         addLine(s, defaultAttributes);
210     }
211 
212     /***
213      * Adds a line of text to the JTextPane using the given SimpleAttributSet.
214      * <br/>
215      * Automatically adds a newline at the end of the string.
216      *
217      * @param s The line to add.
218      * @param attributes The attributes to be used for text formatting.
219      */
220     public void addLine(final String s, final SimpleAttributeSet attributes) {
221         final int chunkSize = 4095;
222         String line = s;
223         while (line.length() > chunkSize) {
224             String start = line.substring(0, chunkSize);
225             add(start + "\n", attributes);
226             line = line.substring(chunkSize + 1);
227         }
228         add(line + "\n", attributes);
229     }
230 
231     /***
232      * Adds a line of text to the JTextPane using the default SimpleAttributSet.
233      * <br/>
234      * Automatically adds a newline at the end of the string.
235      *
236      * @param s The line to add.
237      */
238     public void add(final String s) {
239         add(s, defaultAttributes);
240     }
241 
242     /***
243      * Adds a string to the JTextPane using the given SimpleAttributSet.
244      *
245      * @param s The line to add.
246      * @param attributes The attributes used for text formatting.
247      */
248     public void add(final String s, final SimpleAttributeSet attributes) {
249         try {
250             document.insertString(document.getLength(), s, attributes);
251         } catch (BadLocationException e) {
252             System.out.println(e.toString());
253         }
254     }
255 
256     /***
257      * Not in use. Launches the system's default browser to display the URL
258      * target of a hyperlink.
259      *
260      * @param url The URL to display in the browser.
261      */
262     private void fireHyperlink(final String url) {
263         try {
264             Runtime.getRuntime().exec("cmd /C start " + url);
265         } catch (Exception e) {
266             System.out.println(
267                 "Failed to launch browser for URL '" + url + "'");
268         }
269     }
270 
271     /*(non-Javadoc)
272      * @see de.matthias_burbach.deputy.core.Log#log(int, java.lang.String)
273      */
274     /***
275      * {@inheritDoc}
276      */
277     public void log(final String severity, final String message) {
278         SwingUtilities.invokeLater(new Runnable() {
279             public void run() {
280                 Color color = Color.BLACK;
281                 if (Log.SEVERITY_WARNING.equals(severity)) {
282                     color = Color.BLUE;
283                 }
284                 if (Log.SEVERITY_ERROR.equals(severity)) {
285                     color = Color.RED;
286                 }
287                 StyleConstants.setForeground(defaultAttributes, color);
288                 addLine("[" + severity + "] " + message);
289                 StyleConstants.setForeground(defaultAttributes, Color.BLACK);
290             }
291         });
292     }
293 
294     /***
295      * Clears the contents of this log panel. Happens whenever a project is
296      * opened or when rules are applied on the project in order to delimit the
297      * size of the log output.
298      */
299     public void clear() {
300         try {
301             textPane.getDocument().remove(
302                 0,
303                 textPane.getDocument().getLength());
304         } catch (BadLocationException e) {
305             e.printStackTrace();
306         }
307     }
308 }