001/* 002 * Copyright 2010-2015 Institut Pasteur. 003 * 004 * This file is part of Icy. 005 * 006 * Icy is free software: you can redistribute it and/or modify 007 * it under the terms of the GNU General Public License as published by 008 * the Free Software Foundation, either version 3 of the License, or 009 * (at your option) any later version. 010 * 011 * Icy is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 014 * GNU General Public License for more details. 015 * 016 * You should have received a copy of the GNU General Public License 017 * along with Icy. If not, see <http://www.gnu.org/licenses/>. 018 */ 019package icy.gui.system; 020 021import icy.file.FileUtil; 022import icy.gui.component.ExternalizablePanel; 023import icy.gui.component.button.IcyButton; 024import icy.gui.component.button.IcyToggleButton; 025import icy.gui.frame.progress.ProgressFrame; 026import icy.gui.util.GuiUtil; 027import icy.preferences.GeneralPreferences; 028import icy.resource.ResourceUtil; 029import icy.resource.icon.IcyIcon; 030import icy.system.IcyExceptionHandler; 031import icy.util.EventUtil; 032 033import java.awt.BorderLayout; 034import java.awt.Color; 035import java.awt.Dimension; 036import java.awt.datatransfer.Clipboard; 037import java.awt.datatransfer.ClipboardOwner; 038import java.awt.datatransfer.StringSelection; 039import java.awt.datatransfer.Transferable; 040import java.awt.event.ActionEvent; 041import java.awt.event.ActionListener; 042import java.awt.event.KeyAdapter; 043import java.awt.event.KeyEvent; 044import java.awt.event.MouseAdapter; 045import java.awt.event.MouseEvent; 046import java.io.FileWriter; 047import java.io.IOException; 048import java.io.PrintStream; 049import java.io.Writer; 050import java.util.EventListener; 051 052import javax.swing.Box; 053import javax.swing.JLabel; 054import javax.swing.JPanel; 055import javax.swing.JScrollPane; 056import javax.swing.JSpinner; 057import javax.swing.JTextField; 058import javax.swing.JTextPane; 059import javax.swing.SpinnerNumberModel; 060import javax.swing.event.ChangeEvent; 061import javax.swing.event.ChangeListener; 062import javax.swing.text.BadLocationException; 063import javax.swing.text.Element; 064import javax.swing.text.SimpleAttributeSet; 065import javax.swing.text.StyleConstants; 066import javax.swing.text.StyledDocument; 067 068/** 069 * @author Stephane 070 */ 071public class OutputConsolePanel extends ExternalizablePanel implements ClipboardOwner 072{ 073 public static interface OutputConsoleChangeListener extends EventListener 074 { 075 public void outputConsoleChanged(OutputConsolePanel source, boolean isError); 076 } 077 078 private class WindowsOutPrintStream extends PrintStream 079 { 080 boolean isStdErr; 081 082 public WindowsOutPrintStream(PrintStream out, boolean isStdErr) 083 { 084 super(out); 085 086 this.isStdErr = isStdErr; 087 } 088 089 @Override 090 public void write(byte[] buf, int off, int len) 091 { 092 try 093 { 094 super.write(buf, off, len); 095 096 final String text = new String(buf, off, len); 097 addText(text, isStdErr); 098 // want file log as well ? 099 if (fileLogButton.isSelected() && (logWriter != null)) 100 { 101 // write and save to file immediately 102 logWriter.write(text); 103 logWriter.flush(); 104 } 105 } 106 catch (Throwable t) 107 { 108 addText(t.getMessage(), isStdErr); 109 } 110 } 111 } 112 113 /** 114 * 115 */ 116 private static final long serialVersionUID = 7142067146669860938L; 117 118 final protected JTextPane textPane; 119 final protected StyledDocument doc; 120 final SimpleAttributeSet normalAttributes; 121 final SimpleAttributeSet errorAttributes; 122 123 final protected JSpinner logMaxLineField; 124 final protected JTextField logMaxLineTextField; 125 final public IcyButton clearLogButton; 126 final public IcyButton copyLogButton; 127 final public IcyButton reportLogButton; 128 final public IcyToggleButton scrollLockButton; 129 final public IcyToggleButton fileLogButton; 130 final public JPanel bottomPanel; 131 132 int nbUpdate; 133 Writer logWriter; 134 135 public OutputConsolePanel() 136 { 137 super("Output", "outputConsole"); 138 139 textPane = new JTextPane(); 140 doc = textPane.getStyledDocument(); 141 nbUpdate = 0; 142 143 errorAttributes = new SimpleAttributeSet(); 144 normalAttributes = new SimpleAttributeSet(); 145 146 StyleConstants.setFontFamily(errorAttributes, "arial"); 147 StyleConstants.setFontSize(errorAttributes, 11); 148 StyleConstants.setForeground(errorAttributes, Color.red); 149 150 StyleConstants.setFontFamily(normalAttributes, "arial"); 151 StyleConstants.setFontSize(normalAttributes, 11); 152 StyleConstants.setForeground(normalAttributes, Color.black); 153 154 logMaxLineField = new JSpinner( 155 new SpinnerNumberModel(GeneralPreferences.getOutputLogSize(), 100, 1000000, 100)); 156 logMaxLineTextField = ((JSpinner.DefaultEditor) logMaxLineField.getEditor()).getTextField(); 157 clearLogButton = new IcyButton(new IcyIcon(ResourceUtil.ICON_DELETE)); 158 copyLogButton = new IcyButton(new IcyIcon(ResourceUtil.ICON_DOC_COPY)); 159 reportLogButton = new IcyButton(new IcyIcon(ResourceUtil.ICON_DOC_EXPORT)); 160 scrollLockButton = new IcyToggleButton(new IcyIcon(ResourceUtil.ICON_LOCK_OPEN)); 161 fileLogButton = new IcyToggleButton(new IcyIcon(ResourceUtil.ICON_SAVE)); 162 fileLogButton.setSelected(GeneralPreferences.getOutputLogToFile()); 163 164 // ComponentUtil.setFontSize(textPane, 10); 165 textPane.setEditable(false); 166 167 clearLogButton.setFlat(true); 168 copyLogButton.setFlat(true); 169 reportLogButton.setFlat(true); 170 scrollLockButton.setFlat(true); 171 fileLogButton.setFlat(true); 172 173 logMaxLineField.setPreferredSize(new Dimension(80, 24)); 174 // no focusable 175 logMaxLineField.setFocusable(false); 176 logMaxLineTextField.setFocusable(false); 177 logMaxLineTextField.addMouseListener(new MouseAdapter() 178 { 179 @Override 180 public void mouseClicked(MouseEvent e) 181 { 182 // get focus on double click to enable manual edition 183 if (EventUtil.isLeftMouseButton(e)) 184 { 185 logMaxLineField.setFocusable(true); 186 logMaxLineTextField.setFocusable(true); 187 logMaxLineTextField.requestFocus(); 188 } 189 } 190 }); 191 logMaxLineTextField.addKeyListener(new KeyAdapter() 192 { 193 @Override 194 public void keyPressed(KeyEvent e) 195 { 196 if (e.getKeyCode() == KeyEvent.VK_ESCAPE) 197 { 198 // cancel manual edition ? --> remove focus 199 if (logMaxLineTextField.isFocusable()) 200 { 201 logMaxLineTextField.setFocusable(false); 202 logMaxLineField.setFocusable(false); 203 } 204 } 205 } 206 }); 207 logMaxLineField.addChangeListener(new ChangeListener() 208 { 209 @Override 210 public void stateChanged(ChangeEvent e) 211 { 212 GeneralPreferences.setOutputLogSize(getLogMaxLine()); 213 214 // manual edition ? --> remove focus 215 if (logMaxLineTextField.isFocusable()) 216 { 217 logMaxLineTextField.setFocusable(false); 218 logMaxLineField.setFocusable(false); 219 } 220 221 try 222 { 223 limitLog(); 224 } 225 catch (Exception ex) 226 { 227 // ignore 228 229 } 230 } 231 }); 232 logMaxLineField.setToolTipText("Double-click to edit the maximum number of line (max = 1000000)"); 233 clearLogButton.setToolTipText("Clear all"); 234 copyLogButton.setToolTipText("Copy to clipboard"); 235 reportLogButton.setToolTipText("Report content to dev team"); 236 scrollLockButton.setToolTipText("Scroll Lock"); 237 fileLogButton.setToolTipText("Enable/Disable log file saving (log.txt)"); 238 239 clearLogButton.addActionListener(new ActionListener() 240 { 241 @Override 242 public void actionPerformed(ActionEvent e) 243 { 244 textPane.setText(""); 245 } 246 }); 247 copyLogButton.addActionListener(new ActionListener() 248 { 249 @Override 250 public void actionPerformed(ActionEvent e) 251 { 252 final Clipboard clipboard = getToolkit().getSystemClipboard(); 253 254 clipboard.setContents(new StringSelection(getText()), OutputConsolePanel.this); 255 } 256 }); 257 reportLogButton.addActionListener(new ActionListener() 258 { 259 @Override 260 public void actionPerformed(ActionEvent e) 261 { 262 final ProgressFrame progressFrame = new ProgressFrame("Sending report..."); 263 264 try 265 { 266 // send report 267 IcyExceptionHandler.report(getText()); 268 } 269 finally 270 { 271 progressFrame.close(); 272 } 273 } 274 }); 275 scrollLockButton.addActionListener(new ActionListener() 276 { 277 @Override 278 public void actionPerformed(ActionEvent e) 279 { 280 if (scrollLockButton.isSelected()) 281 scrollLockButton.setIconImage(ResourceUtil.ICON_LOCK_CLOSE); 282 else 283 scrollLockButton.setIconImage(ResourceUtil.ICON_LOCK_OPEN); 284 } 285 }); 286 fileLogButton.addActionListener(new ActionListener() 287 { 288 @Override 289 public void actionPerformed(ActionEvent e) 290 { 291 GeneralPreferences.setOutputLogFile(fileLogButton.isSelected()); 292 } 293 }); 294 295 bottomPanel = GuiUtil.createPageBoxPanel(Box.createVerticalStrut(4), 296 GuiUtil.createLineBoxPanel(clearLogButton, Box.createHorizontalStrut(4), copyLogButton, 297 Box.createHorizontalStrut(4), reportLogButton, Box.createHorizontalGlue(), 298 Box.createHorizontalStrut(4), new JLabel("Limit"), Box.createHorizontalStrut(4), 299 logMaxLineField, Box.createHorizontalStrut(4), scrollLockButton, Box.createHorizontalStrut(4), 300 fileLogButton)); 301 302 final JPanel panel = new JPanel(); 303 panel.setLayout(new BorderLayout()); 304 panel.add(textPane, BorderLayout.CENTER); 305 306 final JScrollPane scrollPane = new JScrollPane(panel); 307 308 setLayout(new BorderLayout()); 309 310 add(scrollPane, BorderLayout.CENTER); 311 add(bottomPanel, BorderLayout.SOUTH); 312 313 validate(); 314 315 // redirect standard output 316 System.setOut(new WindowsOutPrintStream(System.out, false)); 317 System.setErr(new WindowsOutPrintStream(System.err, true)); 318 319 try 320 { 321 // define log file writer (always clear log.txt file if present) 322 logWriter = new FileWriter(FileUtil.getApplicationDirectory() + "/icy.log", false); 323 } 324 catch (IOException e1) 325 { 326 logWriter = null; 327 } 328 } 329 330 public void addText(String text, boolean isError) 331 { 332 try 333 { 334 nbUpdate++; 335 336 // insert text 337 synchronized (doc) 338 { 339 if (isError) 340 doc.insertString(doc.getLength(), text, errorAttributes); 341 else 342 doc.insertString(doc.getLength(), text, normalAttributes); 343 344 // do clean sometime.. 345 if ((nbUpdate & 0x7F) == 0) 346 limitLog(); 347 348 // scroll lock feature 349 if (!scrollLockButton.isSelected()) 350 textPane.setCaretPosition(doc.getLength()); 351 } 352 } 353 catch (Exception e) 354 { 355 // ignore 356 } 357 358 changed(isError); 359 } 360 361 /** 362 * Get console content. 363 */ 364 public String getText() 365 { 366 try 367 { 368 synchronized (doc) 369 { 370 return doc.getText(0, doc.getLength()); 371 } 372 } 373 catch (BadLocationException e) 374 { 375 return ""; 376 } 377 } 378 379 /** 380 * Apply maximum line limitation to the log output 381 * 382 * @throws BadLocationException 383 */ 384 public void limitLog() throws BadLocationException 385 { 386 final Element root = doc.getDefaultRootElement(); 387 final int numLine = root.getElementCount(); 388 final int logMaxLine = getLogMaxLine(); 389 390 // limit to maximum wanted lines 391 if (numLine > logMaxLine) 392 { 393 final Element line = root.getElement(numLine - (logMaxLine + 1)); 394 // remove "old" lines 395 doc.remove(0, line.getEndOffset()); 396 } 397 } 398 399 /** 400 * Returns maximum log line number 401 */ 402 public int getLogMaxLine() 403 { 404 return ((Integer) logMaxLineField.getValue()).intValue(); 405 } 406 407 /** 408 * Sets maximum log line number 409 */ 410 public void setLogMaxLine(int value) 411 { 412 logMaxLineField.setValue(Integer.valueOf(value)); 413 } 414 415 private void changed(boolean isError) 416 { 417 fireChangedEvent(isError); 418 } 419 420 public void fireChangedEvent(boolean isError) 421 { 422 for (OutputConsoleChangeListener listener : listenerList.getListeners(OutputConsoleChangeListener.class)) 423 listener.outputConsoleChanged(this, isError); 424 } 425 426 public void addOutputConsoleChangeListener(OutputConsoleChangeListener listener) 427 { 428 listenerList.add(OutputConsoleChangeListener.class, listener); 429 } 430 431 public void removeOutputConsoleChangeListener(OutputConsoleChangeListener listener) 432 { 433 listenerList.remove(OutputConsoleChangeListener.class, listener); 434 } 435 436 @Override 437 public void lostOwnership(Clipboard clipboard, Transferable contents) 438 { 439 // ignore 440 } 441}