PageRenderTime 54ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

/src/main/java/org/spoutcraft/launcher/skin/ConsoleFrame.java

https://bitbucket.org/dotblank/westeroscraftlauncher
Java | 514 lines | 292 code | 50 blank | 172 comment | 27 complexity | da9e9e9f29db4e633f8eedcf87033d6e MD5 | raw file
  1. /*
  2. * This file is part of Westeroscraft Launcher.
  3. *
  4. * Copyright (c) 2011 Spout LLC <http://www.spout.org/>
  5. * Westeroscraft Launcher is licensed under the Spout License Version 1.
  6. *
  7. * Westeroscraft Launcher is free software: you can redistribute it and/or modify
  8. * it under the terms of the GNU Lesser General Public License as published by
  9. * the Free Software Foundation, either version 3 of the License, or
  10. * (at your option) any later version.
  11. *
  12. * In addition, 180 days after any changes are published, you can use the
  13. * software, incorporating those changes, under the terms of the MIT license,
  14. * as described in the Spout License Version 1.
  15. *
  16. * Westeroscraft Launcher is distributed in the hope that it will be useful,
  17. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  18. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  19. * GNU Lesser General Public License for more details.
  20. *
  21. * You should have received a copy of the GNU Lesser General Public License,
  22. * the MIT license and the Spout License Version 1 along with this program.
  23. * If not, see <http://www.gnu.org/licenses/> for the GNU Lesser General Public
  24. * License and see <http://spout.in/licensev1> for the full license,
  25. * including the MIT license.
  26. */
  27. /*
  28. * This file is part of Spoutcraft Launcher.
  29. *
  30. * Copyright (c) 2011 Spout LLC <http://www.spout.org/>
  31. * Spoutcraft Launcher is licensed under the Spout License Version 1.
  32. *
  33. * Spoutcraft Launcher is free software: you can redistribute it and/or modify
  34. * it under the terms of the GNU Lesser General Public License as published by
  35. * the Free Software Foundation, either version 3 of the License, or
  36. * (at your option) any later version.
  37. *
  38. * In addition, 180 days after any changes are published, you can use the
  39. * software, incorporating those changes, under the terms of the MIT license,
  40. * as described in the Spout License Version 1.
  41. *
  42. * Spoutcraft Launcher is distributed in the hope that it will be useful,
  43. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  44. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  45. * GNU Lesser General Public License for more details.
  46. *
  47. * You should have received a copy of the GNU Lesser General Public License,
  48. * the MIT license and the Spout License Version 1 along with this program.
  49. * If not, see <http://www.gnu.org/licenses/> for the GNU Lesser General Public
  50. * License and see <http://spout.in/licensev1> for the full license,
  51. * including the MIT license.
  52. */
  53. /*
  54. * SK's Minecraft Launcher
  55. * Copyright (C) 2010, 2011 Albert Pham <http://www.sk89q.com>
  56. *
  57. * This program is free software: you can redistribute it and/or modify
  58. * it under the terms of the GNU Lesser General Public License as published by
  59. * the Free Software Foundation, either version 3 of the License, or
  60. * (at your option) any later version.
  61. *
  62. * This program is distributed in the hope that it will be useful,
  63. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  64. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  65. * GNU General Public License for more details.
  66. *
  67. * You should have received a copy of the GNU General Public License
  68. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  69. */
  70. package org.spoutcraft.launcher.skin;
  71. import java.awt.BorderLayout;
  72. import java.awt.Color;
  73. import java.awt.Dimension;
  74. import java.awt.Font;
  75. import java.awt.Toolkit;
  76. import java.awt.event.ActionEvent;
  77. import java.awt.event.ActionListener;
  78. import java.awt.event.MouseEvent;
  79. import java.awt.event.MouseListener;
  80. import java.awt.event.WindowAdapter;
  81. import java.awt.event.WindowEvent;
  82. import java.io.ByteArrayOutputStream;
  83. import java.io.IOException;
  84. import java.io.InputStream;
  85. import java.io.PrintWriter;
  86. import java.io.StringWriter;
  87. import java.io.Writer;
  88. import java.util.logging.Handler;
  89. import java.util.logging.Level;
  90. import java.util.logging.LogRecord;
  91. import java.util.logging.Logger;
  92. import javax.swing.JFrame;
  93. import javax.swing.JMenuItem;
  94. import javax.swing.JPopupMenu;
  95. import javax.swing.JScrollPane;
  96. import javax.swing.JTextArea;
  97. import javax.swing.JTextPane;
  98. import javax.swing.ScrollPaneConstants;
  99. import javax.swing.WindowConstants;
  100. import javax.swing.text.AttributeSet;
  101. import javax.swing.text.BadLocationException;
  102. import javax.swing.text.DefaultCaret;
  103. import javax.swing.text.Document;
  104. import javax.swing.text.JTextComponent;
  105. import javax.swing.text.SimpleAttributeSet;
  106. import javax.swing.text.StyleConstants;
  107. import org.apache.commons.io.IOUtils;
  108. import org.spoutcraft.launcher.skin.components.LoginFrame;
  109. import org.spoutcraft.launcher.util.Compatibility;
  110. /**
  111. * Console dialog for showing console messages.
  112. *
  113. * @author sk89q
  114. *
  115. * This code reused and relicensed as LGPLv3 with permission.
  116. */
  117. public class ConsoleFrame extends JFrame implements MouseListener {
  118. private static final long serialVersionUID = 1L;
  119. private static final Logger rootLogger = Logger.getLogger("launcher");
  120. private Process trackProc;
  121. private Handler loggerHandler;
  122. private JTextComponent textComponent;
  123. private Document document;
  124. private int numLines;
  125. private boolean colorEnabled = false;
  126. private final SimpleAttributeSet defaultAttributes = new SimpleAttributeSet();
  127. private final SimpleAttributeSet highlightedAttributes;
  128. private final SimpleAttributeSet errorAttributes;
  129. private final SimpleAttributeSet infoAttributes;
  130. private final SimpleAttributeSet debugAttributes;
  131. /**
  132. * Construct the frame.
  133. *
  134. * @param numLines number of lines to show at a time
  135. * @param colorEnabled true to enable a colored console
  136. */
  137. public ConsoleFrame(int numLines, boolean colorEnabled) {
  138. this(numLines, colorEnabled, null, false);
  139. }
  140. /**
  141. * Construct the frame.
  142. *
  143. * @param numLines number of lines to show at a time
  144. * @param colorEnabled true to enable a colored console
  145. * @param trackProc process to track
  146. * @param killProcess true to kill the process on console close
  147. */
  148. public ConsoleFrame(int numLines, boolean colorEnabled, final Process trackProc, final boolean killProcess) {
  149. super("Spoutcraft Console");
  150. this.numLines = numLines;
  151. this.colorEnabled = colorEnabled;
  152. this.trackProc = trackProc;
  153. this.highlightedAttributes = new SimpleAttributeSet();
  154. StyleConstants.setForeground(highlightedAttributes, Color.BLACK);
  155. StyleConstants.setBackground(highlightedAttributes, Color.YELLOW);
  156. this.errorAttributes = new SimpleAttributeSet();
  157. StyleConstants.setForeground(errorAttributes, new Color(200, 0, 0));
  158. this.infoAttributes = new SimpleAttributeSet();
  159. StyleConstants.setForeground(infoAttributes, new Color(200, 0, 0));
  160. this.debugAttributes = new SimpleAttributeSet();
  161. StyleConstants.setForeground(debugAttributes, Color.DARK_GRAY);
  162. setSize(new Dimension(650, 400));
  163. buildUI();
  164. Compatibility.setIconImage(this, Toolkit.getDefaultToolkit().getImage(LoginFrame.spoutcraftIcon));
  165. if (trackProc != null) {
  166. track(trackProc);
  167. }
  168. addMouseListener(this);
  169. setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
  170. addWindowListener(new WindowAdapter() {
  171. @Override
  172. public void windowClosing(WindowEvent event) {
  173. if (trackProc != null && killProcess) {
  174. trackProc.destroy();
  175. if (loggerHandler != null) {
  176. rootLogger.removeHandler(loggerHandler);
  177. }
  178. event.getWindow().dispose();
  179. }
  180. }
  181. });
  182. }
  183. /**
  184. * Build the interface.
  185. */
  186. private void buildUI() {
  187. if (colorEnabled) {
  188. JTextPane text = new JTextPane();
  189. this.textComponent = text;
  190. } else {
  191. JTextArea text = new JTextArea();
  192. this.textComponent = text;
  193. text.setLineWrap(true);
  194. }
  195. textComponent.addMouseListener(this);
  196. textComponent.setFont(getMonospaceFont());
  197. textComponent.setEditable(false);
  198. DefaultCaret caret = (DefaultCaret) textComponent.getCaret();
  199. caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
  200. document = textComponent.getDocument();
  201. document.addDocumentListener(new LimitLinesDocumentListener(numLines, true));
  202. JScrollPane scrollText = new JScrollPane(textComponent);
  203. scrollText.setBorder(null);
  204. scrollText.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
  205. add(scrollText, BorderLayout.CENTER);
  206. }
  207. /**
  208. * Log a message.
  209. *
  210. * @param line line
  211. */
  212. public void log(String line) {
  213. log(line, null);
  214. }
  215. /**
  216. * Log a message given the {@link AttributeSet}.
  217. *
  218. * @param line line
  219. * @param attributes attribute set, or null for none
  220. */
  221. public void log(String line, AttributeSet attributes) {
  222. if (colorEnabled && line.startsWith("(!!)")) {
  223. attributes = highlightedAttributes;
  224. }
  225. try {
  226. int offset = document.getLength();
  227. document.insertString(offset, line, (attributes != null && colorEnabled) ? attributes : defaultAttributes);
  228. textComponent.setCaretPosition(document.getLength());
  229. } catch (BadLocationException ble) {
  230. } catch (NullPointerException npe) {
  231. }
  232. }
  233. /**
  234. * Get an output stream that can be written to.
  235. *
  236. * @return output stream
  237. */
  238. public ConsoleOutputStream getOutputStream() {
  239. return getOutputStream((AttributeSet) null);
  240. }
  241. /**
  242. * Get an output stream with the given attribute set.
  243. *
  244. * @param attributes attributes
  245. * @return output stream
  246. */
  247. public ConsoleOutputStream getOutputStream(AttributeSet attributes) {
  248. return new ConsoleOutputStream(attributes);
  249. }
  250. /**
  251. * Get an output stream using the give color.
  252. *
  253. * @param color color to use
  254. * @return output stream
  255. */
  256. public ConsoleOutputStream getOutputStream(Color color) {
  257. SimpleAttributeSet attributes = new SimpleAttributeSet();
  258. StyleConstants.setForeground(attributes, color);
  259. return getOutputStream(attributes);
  260. }
  261. /**
  262. * Consume an input stream and print it to the dialog. The consumer
  263. * will be in a separate daemon thread.
  264. *
  265. * @param from stream to read
  266. */
  267. public void consume(InputStream from) {
  268. consume(from, getOutputStream());
  269. }
  270. /**
  271. * Consume an input stream and print it to the dialog. The consumer
  272. * will be in a separate daemon thread.
  273. *
  274. * @param from stream to read
  275. * @param color color to use
  276. */
  277. public void consume(InputStream from, Color color) {
  278. consume(from, getOutputStream(color));
  279. }
  280. /**
  281. * Consume an input stream and print it to the dialog. The consumer
  282. * will be in a separate daemon thread.
  283. *
  284. * @param from stream to read
  285. * @param attributes attributes
  286. */
  287. public void consume(InputStream from, AttributeSet attributes) {
  288. consume(from, getOutputStream(attributes));
  289. }
  290. /**
  291. * Internal method to consume a stream.
  292. *
  293. * @param from stream to consume
  294. * @param outputStream console stream to write to
  295. */
  296. private void consume(InputStream from, ConsoleOutputStream outputStream) {
  297. final InputStream in = from;
  298. final PrintWriter out = new PrintWriter(outputStream, true);
  299. Thread thread = new Thread(new Runnable() {
  300. public void run() {
  301. byte[] buffer = new byte[1024];
  302. try {
  303. int len;
  304. while ((len = in.read(buffer)) != -1) {
  305. String s = new String(buffer, 0, len);
  306. System.out.print(s);
  307. out.append(s);
  308. out.flush();
  309. }
  310. } catch (IOException e) {
  311. } finally {
  312. IOUtils.closeQuietly(in);
  313. IOUtils.closeQuietly(out);
  314. }
  315. }
  316. });
  317. thread.setDaemon(true);
  318. thread.start();
  319. }
  320. /**
  321. * Track a process in a separate daemon thread.
  322. *
  323. * @param process process
  324. */
  325. private void track(Process process) {
  326. final PrintWriter out = new PrintWriter(getOutputStream(Color.MAGENTA), true);
  327. Thread thread = new Thread(new Runnable() {
  328. public void run() {
  329. try {
  330. int code = trackProc.waitFor();
  331. out.println("Process ended with code " + code);
  332. } catch (InterruptedException e) {
  333. out.println("Process tracking interrupted!");
  334. }
  335. }
  336. });
  337. thread.setDaemon(true);
  338. thread.start();
  339. }
  340. /**
  341. * Registera global logger listener.
  342. */
  343. public void registerLoggerHandler() {
  344. for (Handler handler : rootLogger.getHandlers()) {
  345. rootLogger.removeHandler(handler);
  346. }
  347. loggerHandler = new ConsoleLoggerHandler();
  348. rootLogger.addHandler(loggerHandler);
  349. }
  350. /**
  351. * Used to send console messages to the console.
  352. */
  353. public final class ConsoleOutputStream extends ByteArrayOutputStream {
  354. private AttributeSet attributes;
  355. private ConsoleOutputStream(AttributeSet attributes) {
  356. this.attributes = attributes;
  357. }
  358. @Override
  359. public void flush() {
  360. String data = toString();
  361. if (data.length() == 0) return;
  362. log(data, attributes);
  363. reset();
  364. }
  365. }
  366. /**
  367. * Used to send logger messages to the console.
  368. */
  369. private class ConsoleLoggerHandler extends Handler {
  370. @Override
  371. public void publish(LogRecord record) {
  372. Level level = record.getLevel();
  373. Throwable t = record.getThrown();
  374. AttributeSet attributes = defaultAttributes;
  375. if (level.intValue() >= Level.WARNING.intValue()) {
  376. attributes = errorAttributes;
  377. } else if (level.intValue() < Level.INFO.intValue()) {
  378. attributes = debugAttributes;
  379. }
  380. log(record.getMessage() + "\n", attributes);
  381. if (t != null) {
  382. log(getStackTrace(t) + "\n", attributes);
  383. }
  384. }
  385. @Override
  386. public void flush() {
  387. }
  388. @Override
  389. public void close() throws SecurityException {
  390. }
  391. }
  392. private static String[] monospaceFontNames = {"Consolas", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", "Lucida Console"};
  393. /**
  394. * Get a supported monospace font.
  395. *
  396. * @return font
  397. */
  398. public static Font getMonospaceFont() {
  399. for (String fontName : monospaceFontNames) {
  400. Font font = Font.decode(fontName + "-11");
  401. if (!font.getFamily().equalsIgnoreCase("Dialog")) {
  402. return font;
  403. }
  404. }
  405. return new Font("Monospace", Font.PLAIN, 11);
  406. }
  407. /**
  408. * Get a stack trace as a string.
  409. *
  410. * @param t exception
  411. * @return stack trace
  412. */
  413. public static String getStackTrace(Throwable t) {
  414. Writer result = new StringWriter();
  415. try {
  416. PrintWriter printWriter = new PrintWriter(result);
  417. t.printStackTrace(printWriter);
  418. } finally {
  419. IOUtils.closeQuietly(result);
  420. }
  421. return result.toString();
  422. }
  423. public void mousePressed(MouseEvent e) {
  424. if (e.isPopupTrigger()) {
  425. doPop(e);
  426. }
  427. }
  428. public void mouseReleased(MouseEvent e) {
  429. if (e.isPopupTrigger()) {
  430. doPop(e);
  431. }
  432. }
  433. public void mouseClicked(MouseEvent e) {
  434. }
  435. public void mouseEntered(MouseEvent e) {
  436. }
  437. public void mouseExited(MouseEvent e) {
  438. }
  439. private void doPop(MouseEvent e) {
  440. ContextMenu menu = new ContextMenu();
  441. menu.show(e.getComponent(), e.getX(), e.getY());
  442. }
  443. private class ContextMenu extends JPopupMenu {
  444. private static final long serialVersionUID = 1L;
  445. JMenuItem copy;
  446. JMenuItem clear;
  447. public ContextMenu() {
  448. copy = new JMenuItem("Copy");
  449. add(copy);
  450. copy.addActionListener(new ActionListener() {
  451. public void actionPerformed(ActionEvent e) {
  452. textComponent.copy();
  453. }
  454. });
  455. clear = new JMenuItem("Clear");
  456. add(clear);
  457. clear.addActionListener(new ActionListener() {
  458. public void actionPerformed(ActionEvent e) {
  459. textComponent.setText("");
  460. }
  461. });
  462. }
  463. }
  464. }