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
69
70 defaultAttributes = new SimpleAttributeSet();
71 StyleConstants.setBold(defaultAttributes, false);
72 StyleConstants.setFontFamily(defaultAttributes, "Courier");
73 StyleConstants.setForeground(defaultAttributes, Color.BLACK);
74
75
76
77
78 document = new DefaultStyledDocument();
79
80 document.addDocumentListener(new DocumentListener() {
81 public void changedUpdate(final DocumentEvent e) {
82
83 }
84 public void insertUpdate(final DocumentEvent e) {
85 textPane.setCaretPosition(document.getLength());
86 }
87 public void removeUpdate(final DocumentEvent e) {
88
89 }
90 });
91
92
93
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
108
109
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
126
127
128
129 /***
130 * {@inheritDoc}
131 */
132 public void mouseDragged(final MouseEvent e) {
133
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
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
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
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
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
291
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 }