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
68
69 defaultAttributes = new SimpleAttributeSet();
70 StyleConstants.setBold(defaultAttributes, false);
71 StyleConstants.setFontFamily(defaultAttributes, "Courier");
72 StyleConstants.setForeground(defaultAttributes, Color.BLACK);
73
74
75
76
77 document = new DefaultStyledDocument();
78
79 document.addDocumentListener(new DocumentListener() {
80 public void changedUpdate(final DocumentEvent e) {
81
82 }
83 public void insertUpdate(final DocumentEvent e) {
84 textPane.setCaretPosition(document.getLength());
85 }
86 public void removeUpdate(final DocumentEvent e) {
87
88 }
89 });
90
91
92
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
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
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
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
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
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
272
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 }