View Javadoc

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