001package com.ganteater.ae.desktop.view; 002 003import java.awt.BorderLayout; 004import java.awt.Color; 005import java.awt.Component; 006import java.awt.Dimension; 007import java.awt.Font; 008import java.awt.Toolkit; 009import java.awt.datatransfer.Clipboard; 010import java.awt.datatransfer.StringSelection; 011import java.awt.event.ActionEvent; 012import java.awt.event.ActionListener; 013import java.awt.event.KeyAdapter; 014import java.awt.event.KeyEvent; 015import java.awt.event.MouseAdapter; 016import java.awt.event.MouseEvent; 017import java.io.IOException; 018import java.io.PrintWriter; 019import java.io.StringWriter; 020import java.text.SimpleDateFormat; 021import java.util.Date; 022import java.util.HashMap; 023import java.util.Map; 024 025import javax.swing.BorderFactory; 026import javax.swing.JButton; 027import javax.swing.JCheckBox; 028import javax.swing.JComboBox; 029import javax.swing.JLabel; 030import javax.swing.JMenuItem; 031import javax.swing.JPanel; 032import javax.swing.JPopupMenu; 033import javax.swing.JScrollBar; 034import javax.swing.JScrollPane; 035import javax.swing.JTable; 036import javax.swing.JTextArea; 037import javax.swing.JTextField; 038import javax.swing.ListSelectionModel; 039import javax.swing.UIDefaults; 040import javax.swing.table.AbstractTableModel; 041import javax.swing.table.TableCellRenderer; 042import javax.swing.table.TableColumnModel; 043 044import org.apache.commons.lang.ObjectUtils; 045import org.apache.commons.lang.StringUtils; 046 047import com.ganteater.ae.AELogRecord; 048import com.ganteater.ae.AEWorkspace; 049import com.ganteater.ae.desktop.editor.TaskEditor; 050import com.ganteater.ae.desktop.ui.Button; 051import com.ganteater.ae.desktop.ui.LogFrame; 052import com.ganteater.ae.desktop.ui.TextPrompt; 053import com.ganteater.ae.desktop.util.UIUtils; 054 055public class ListLogView extends LogView implements ActionListener { 056 057 private static final long serialVersionUID = 1L; 058 059 private static final Font LOG_FONT = new Font("Monospaced", Font.PLAIN, 11); 060 061 private LogList logList; 062 063 private JComboBox<String> levelBox = new JComboBox<String>(new String[] { "Debug", "Info", "Warning", "Error" }); 064 private JCheckBox autoformatBtn = new JCheckBox("Auto format"); 065 private JTextField find = new JTextField(8); 066 067 private JTable fLogTextArea = new LogTable(); 068 069 private JCheckBox fLogToEnd = new JCheckBox("Auto scrolling"); 070 private JCheckBox fDeleteOldRec = new JCheckBox("Limit 50 records"); 071 072 private JScrollPane fLogScrollPanel = new JScrollPane(); 073 074 private TaskEditor fTaskEditor; 075 076 private JLabel messageBar = new JLabel(); 077 078 private boolean mainLog; 079 080 public ListLogView(TaskEditor aTaskEditor, String aName) { 081 this(aTaskEditor, aName, false); 082 } 083 084 public ListLogView(TaskEditor aTaskEditor, String aName, boolean mainLog) { 085 super(aName, aTaskEditor.getConfigNode()); 086 this.mainLog = mainLog; 087 fTaskEditor = aTaskEditor; 088 levelBox.setPreferredSize(new Dimension(60, 18)); 089 find.setPreferredSize(new Dimension(162, 18)); 090 createPanel(); 091 } 092 093 private void copyToClipboard() { 094 ListSelectionModel selectionModel = fLogTextArea.getSelectionModel(); 095 int minSelectionIndex = selectionModel.getMinSelectionIndex(); 096 int maxSelectionIndex = selectionModel.getMaxSelectionIndex(); 097 098 StringBuilder msgBuilder = new StringBuilder(); 099 for (int i = minSelectionIndex; i <= maxSelectionIndex; i++) { 100 101 boolean selectedIndex = selectionModel.isSelectedIndex(i); 102 103 if (selectedIndex) { 104 if (i != minSelectionIndex) { 105 msgBuilder.append("\n---------------------\n"); 106 } 107 108 LogRecord text = logList.get(i); 109 TextLogView logPanel = new TextLogView(fTaskEditor, getName()); 110 logPanel.info(text, fTaskEditor.getTaskProcessor()); 111 boolean selected = autoformatBtn.isSelected(); 112 if (selected) { 113 logPanel.format(); 114 } 115 116 String msg = ((JTextArea) logPanel.getLogTextArea()).getText(); 117 msgBuilder.append(msg); 118 } 119 } 120 121 StringSelection stringSelection = new StringSelection(msgBuilder.toString()); 122 Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); 123 clipboard.setContents(stringSelection, null); 124 } 125 126 public void actionPerformed(ActionEvent e) { 127 if (e != null && e.getSource() == fLogToEnd) { 128 AEWorkspace.getInstance().setDefaultUserConfiguration(".Edit.LogToEnd", 129 fLogToEnd.isSelected() ? "true" : "false"); 130 return; 131 } 132 if (e != null && e.getSource() == fDeleteOldRec) { 133 AEWorkspace.getInstance().setDefaultUserConfiguration(".Edit.DeleteOldRec", 134 fDeleteOldRec.isSelected() ? "true" : "false"); 135 return; 136 } 137 } 138 139 @Override 140 public Object info(Object o) { 141 appendText(2, o); 142 return super.info(o); 143 } 144 145 @Override 146 public Object debug(Object o) { 147 if (o instanceof Throwable) { 148 StringWriter theStringWriter = new StringWriter(); 149 ((Throwable) o).printStackTrace(new PrintWriter(theStringWriter)); 150 appendText(3, theStringWriter.toString()); 151 return o; 152 153 } else { 154 appendText(3, o); 155 return super.debug(o); 156 } 157 } 158 159 @Override 160 public Object debug(Object o, Throwable aThrowable) { 161 appendText(3, o); 162 163 StringWriter theStringWriter = new StringWriter(); 164 aThrowable.printStackTrace(new PrintWriter(theStringWriter)); 165 appendText(3, theStringWriter.toString()); 166 167 return super.error(o, aThrowable); 168 } 169 170 @Override 171 public Object error(Object o) { 172 appendText(0, o); 173 return super.error(o); 174 } 175 176 @Override 177 public Object error(Object o, Throwable aThrowable) { 178 StringWriter theStringWriter = new StringWriter(); 179 aThrowable.printStackTrace(new PrintWriter(theStringWriter)); 180 appendText(0, ObjectUtils.toString(o) + "\n" + theStringWriter.toString()); 181 return super.error(o, aThrowable); 182 } 183 184 @Override 185 public Object warn(Object o) { 186 appendText(1, o); 187 return super.error(o); 188 } 189 190 void appendText(int logLevel, Object o) { 191 logList.add(new LogRecord(logLevel, o), fDeleteOldRec.isSelected()); 192 193 int level = 3 - levelBox.getSelectedIndex(); 194 if (logLevel <= level) { 195 fLogTextArea.revalidate(); 196 fLogScrollPanel.repaint(); 197 } 198 199 if (fLogToEnd.isSelected()) { 200 JScrollBar verticalScrollBar = fLogScrollPanel.getVerticalScrollBar(); 201 verticalScrollBar.setValue(verticalScrollBar.getMaximum()); 202 } 203 } 204 205 public void warn(Object o, Throwable aThrowable) { 206 appendText(1, o); 207 StringWriter theStringWriter = new StringWriter(); 208 aThrowable.printStackTrace(new PrintWriter(theStringWriter)); 209 appendText(1, theStringWriter.toString()); 210 super.error(o, aThrowable); 211 } 212 213 public void createPanel() { 214 String defaultUserConfiguration = AEWorkspace.getInstance().getDefaultUserConfiguration(".defaultLogLevel", 215 "2"); 216 int defLevel = Integer.parseInt(defaultUserConfiguration); 217 logList = new LogList(defLevel); 218 219 logList.setLevel(defLevel); 220 levelBox.setSelectedIndex(3 - defLevel); 221 levelBox.setFont(LOG_FONT); 222 223 fLogTextArea.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); 224 fLogTextArea.setCellSelectionEnabled(true); 225 226 fLogTextArea.addKeyListener(new KeyAdapter() { 227 @Override 228 public void keyPressed(KeyEvent e) { 229 if (e.isControlDown() && e.isShiftDown() && e.getKeyCode() == KeyEvent.VK_C) { 230 copyToClipboard(); 231 } 232 } 233 234 }); 235 236 fLogTextArea.addMouseListener(new MouseAdapter() { 237 238 public void mouseClicked(MouseEvent e) { 239 if (e.getButton() == 1) { 240 241 if (e.getClickCount() == 2) { 242 LogRecord logRecord = selectAndGetRecord(e); 243 if (!e.isControlDown()) { 244 openLogRecord(logRecord); 245 246 } else { 247 openIn(logRecord); 248 } 249 } 250 251 } else if (e.getButton() == 3) { 252 LogRecord logRecord = selectAndGetRecord(e); 253 254 final JPopupMenu popup = new JPopupMenu(); 255 JMenuItem jMenuItem = new JMenuItem("Open"); 256 jMenuItem.addActionListener(event -> openLogRecord(logRecord)); 257 popup.add(jMenuItem); 258 jMenuItem = new JMenuItem("System TextEditor"); 259 jMenuItem.addActionListener(event -> openIn(logRecord)); 260 popup.add(jMenuItem); 261 jMenuItem = new JMenuItem("Copy"); 262 jMenuItem.addActionListener(event -> copyToClipboard()); 263 popup.add(jMenuItem); 264 jMenuItem = new JMenuItem("Save"); 265 jMenuItem.addActionListener(event -> { 266 ListSelectionModel selectionModel = fLogTextArea.getSelectionModel(); 267 int minSelectionIndex = selectionModel.getMinSelectionIndex(); 268 int maxSelectionIndex = selectionModel.getMaxSelectionIndex(); 269 270 for (int i = minSelectionIndex; i <= maxSelectionIndex; i++) { 271 272 boolean selectedIndex = selectionModel.isSelectedIndex(i); 273 274 if (selectedIndex) { 275 LogRecord rec = logList.get(i); 276 TextLogView logPanel = new TextLogView(fTaskEditor, rec.getSource()); 277 278 logPanel.info(rec, fTaskEditor.getTaskProcessor()); 279 boolean selected = autoformatBtn.isSelected(); 280 if (selected) { 281 logPanel.format(); 282 } 283 logPanel.saveToFile(); 284 } 285 } 286 }); 287 popup.add(jMenuItem); 288 popup.show(e.getComponent(), e.getX(), e.getY()); 289 } 290 } 291 292 private LogRecord selectAndGetRecord(MouseEvent e) { 293 int r = fLogTextArea.rowAtPoint(e.getPoint()); 294 if (r >= 0 && r < fLogTextArea.getRowCount()) { 295 fLogTextArea.setRowSelectionInterval(r, r); 296 } else { 297 fLogTextArea.clearSelection(); 298 } 299 ListSelectionModel selectionModel = fLogTextArea.getSelectionModel(); 300 int anchorSelectionIndex = selectionModel.getAnchorSelectionIndex(); 301 return logList.get(anchorSelectionIndex); 302 } 303 304 private void openLogRecord(LogRecord logRecord) { 305 String source = logRecord.getSource(); 306 source = StringUtils.defaultString(source); 307 TextLogView logPanel = new TextLogView(fTaskEditor, source); 308 LogFrame logFrame = new LogFrame(logPanel); 309 logFrame.setVisible(true); 310 logFrame.setAlwaysOnTop(true); 311 logFrame.setAlwaysOnTop(false); 312 313 logFrame.showText(logRecord, fTaskEditor.getTaskProcessor(), autoformatBtn.isSelected()); 314 } 315 316 private void openIn(LogRecord text) { 317 try { 318 TextLogView.openFile(text, text.getType()); 319 } catch (IOException e1) { 320 e1.printStackTrace(); 321 } 322 } 323 }); 324 325 TableColumnModel columnModel = fLogTextArea.getColumnModel(); 326 columnModel.getColumn(0).setMinWidth(0); 327 columnModel.getColumn(0).setPreferredWidth(8); 328 columnModel.getColumn(0).setMaxWidth(100); 329 columnModel.getColumn(0).setHeaderValue("Time"); 330 columnModel.getColumn(1).setHeaderValue("Message"); 331 columnModel.getColumn(2).setHeaderValue("Size, B"); 332 columnModel.getColumn(2).setMinWidth(0); 333 columnModel.getColumn(2).setMaxWidth(150); 334 columnModel.getColumn(2).setPreferredWidth(8); 335 336 fLogScrollPanel.getVerticalScrollBar().setDoubleBuffered(true); 337 338 add(fLogScrollPanel, BorderLayout.CENTER); 339 340 JPanel theFindPanel = new JPanel(); 341 342 theFindPanel.add(find); 343 new TextPrompt("Search", find); 344 345 find.addKeyListener(new KeyAdapter() { 346 @Override 347 public void keyPressed(KeyEvent e) { 348 if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { 349 find.setText(""); 350 } 351 fLogTextArea.repaint(); 352 } 353 }); 354 levelBox.addActionListener(e -> { 355 ListSelectionModel selectionModel = fLogTextArea.getSelectionModel(); 356 selectionModel.clearSelection(); 357 int logLevel = 3 - levelBox.getSelectedIndex(); 358 AEWorkspace.getInstance().setDefaultUserConfiguration(".defaultLogLevel", Integer.toString(logLevel)); 359 logList.setLevel(logLevel); 360 fLogTextArea.revalidate(); 361 fLogScrollPanel.revalidate(); 362 }); 363 theFindPanel.add(levelBox); 364 365 JButton theClearButton = new Button("clean.png"); 366 theClearButton.setBorder(BorderFactory.createEmptyBorder()); 367 theClearButton.setFont(LOG_FONT); 368 theClearButton.addActionListener(e -> { 369 ListSelectionModel selectionModel = fLogTextArea.getSelectionModel(); 370 selectionModel.clearSelection(); 371 logList.clear(); 372 fLogTextArea.revalidate(); 373 fLogScrollPanel.revalidate(); 374 }); 375 376 theFindPanel.add(theClearButton); 377 378 if (!mainLog) { 379 JButton theCloseButton = new Button("close.png"); 380 theCloseButton.setBorder(BorderFactory.createEmptyBorder()); 381 theCloseButton.setFont(LOG_FONT); 382 theCloseButton.addActionListener(e -> fTaskEditor.removeActivePresentationPanel()); 383 384 theFindPanel.add(theCloseButton); 385 } 386 387 JPanel theLogControlPanel = new JPanel(new BorderLayout()); 388 theLogControlPanel.add(theFindPanel, BorderLayout.EAST); 389 JPanel theLogLevelPanel = new JPanel(); 390 391 theLogControlPanel.add(theLogLevelPanel, BorderLayout.WEST); 392 393 add(theLogControlPanel, BorderLayout.NORTH); 394 395 JPanel panel = new JPanel(new BorderLayout()); 396 JPanel panel4 = new JPanel(); 397 398 panel4.add(fLogToEnd); 399 panel4.add(fDeleteOldRec); 400 401 autoformatBtn.setSelected( 402 "true".equals(AEWorkspace.getInstance().getDefaultUserConfiguration(".Edit.AutoFormat", "false"))); 403 autoformatBtn.addChangeListener(e -> AEWorkspace.getInstance().setDefaultUserConfiguration(".Edit.AutoFormat", 404 autoformatBtn.isSelected() ? "true" : "false")); 405 406 panel4.add(autoformatBtn); 407 408 panel4.add(this.messageBar); 409 panel.add(panel4, BorderLayout.WEST); 410 add(panel, BorderLayout.SOUTH); 411 fLogToEnd.setSelected( 412 "true".equals(AEWorkspace.getInstance().getDefaultUserConfiguration(".Edit.LogToEnd", "true"))); 413 fLogToEnd.addActionListener(this); 414 415 fDeleteOldRec.setSelected( 416 "true".equals(AEWorkspace.getInstance().getDefaultUserConfiguration(".Edit.DeleteOldRec", "true"))); 417 fDeleteOldRec.addActionListener(this); 418 419 fLogTextArea.setFont(LOG_FONT); 420 fLogScrollPanel.getViewport().add(fLogTextArea); 421 } 422 423 public ListLogView copyAndClean() { 424 ListLogView listLogPresenter = new ListLogView(fTaskEditor, getName()); 425 listLogPresenter.setLogList(logList); 426 String defaultUserConfiguration = AEWorkspace.getInstance().getDefaultUserConfiguration(".defaultLogLevel", 427 "2"); 428 int defLevel = Integer.parseInt(defaultUserConfiguration); 429 logList = new LogList(defLevel); 430 fLogTextArea.revalidate(); 431 fLogScrollPanel.repaint(); 432 433 return listLogPresenter; 434 } 435 436 private void setLogList(LogList logList) { 437 this.logList = logList; 438 fLogToEnd.setVisible(false); 439 fDeleteOldRec.setVisible(false); 440 } 441 442 private final class LogTable extends JTable { 443 private static final long serialVersionUID = 1L; 444 445 private LogTable() { 446 super(new LogTableModel()); 447 } 448 449 @Override 450 public Component prepareRenderer(TableCellRenderer renderer, int row, int column) { 451 Component c = super.prepareRenderer(renderer, row, column); 452 453 UIDefaults defaults = javax.swing.UIManager.getDefaults(); 454 Color background = defaults.getColor("List.background"); 455 Color foreground = defaults.getColor("List.foreground"); 456 457 LogRecord logRecord = logList.get(row); 458 int level = logRecord.getLogLevel(); 459 460 switch (column) { 461 case 0: 462 long time = logRecord.getTime(); 463 double a = time / 500.0; 464 double b = (Math.sin(a) + 1); 465 float r = 1 - (float) b / 2; 466 background = new Color(r, r, r); 467 foreground = UIUtils.getContrastColor(background); 468 break; 469 470 case 2: 471 String text = StringUtils.defaultIfEmpty(logRecord.toString(), ""); 472 a = (double) (text.length()) / 100; 473 if (a < 1) { 474 a = 1; 475 } 476 b = Math.log(a) * 8; 477 b = (100 - b / 2) / 100; 478 background = new Color((float) b, (float) b, (float) b); 479 foreground = UIUtils.getContrastColor(background); 480 break; 481 } 482 483 if (column == 1) { 484 switch (level) { 485 case 0: 486 foreground = tuneColorByBackground(Color.RED); 487 break; 488 case 1: 489 foreground = tuneColorByBackground(Color.BLUE); 490 break; 491 case 2: 492 foreground = tuneColorByBackground(Color.GREEN); 493 break; 494 case 3: 495 break; 496 default: 497 } 498 499 String text = find.getText(); 500 if (StringUtils.isNotBlank(text)) { 501 String message = ObjectUtils.toString(logRecord.getMessage()); 502 if (logRecord.getSource() != null) { 503 message = logRecord.getSource() + " = " + message; 504 } 505 int indexOf = StringUtils.indexOfIgnoreCase(message, text); 506 if (indexOf >= 0) { 507 background = Color.yellow; 508 foreground = UIUtils.getContrastColor(background); 509 } 510 } 511 } 512 513 ListSelectionModel selectionModel = fLogTextArea.getSelectionModel(); 514 515 if (selectionModel.isSelectedIndex(row) && column == 1) { 516 c.setBackground(foreground); 517 if (background == Color.yellow) { 518 c.setForeground(background); 519 } else { 520 Color contrastColor = UIUtils.getContrastColor(foreground); 521 c.setForeground(contrastColor); 522 } 523 } else { 524 c.setBackground(background); 525 c.setForeground(foreground); 526 } 527 528 return c; 529 } 530 531 private Color tuneColorByBackground(Color fgc) { 532 UIDefaults defaults = javax.swing.UIManager.getDefaults(); 533 Color background = defaults.getColor("List.background"); 534 535 int alpha = (int) (0.2126 * background.getRed() + 0.7152 * background.getGreen() 536 + 0.0722 * background.getBlue()); 537 538 if (alpha < 60) { 539 int colorLevel = 140; 540 fgc = new Color(fgc.getRed() == 0 ? colorLevel : fgc.getRed(), 541 fgc.getGreen() == 0 ? colorLevel : fgc.getGreen(), 542 fgc.getBlue() == 0 ? colorLevel : fgc.getBlue()); 543 } 544 545 if (alpha > 200) { 546 fgc = fgc.darker().darker(); 547 } 548 return fgc; 549 } 550 } 551 552 private final class LogTableModel extends AbstractTableModel { 553 private static final long serialVersionUID = 1L; 554 SimpleDateFormat simpleDateFormat = new SimpleDateFormat("HH.mm.ss,SSS"); 555 556 public int getColumnCount() { 557 return 3; 558 } 559 560 public int getRowCount() { 561 return logList.getSize(); 562 } 563 564 public Object getValueAt(int rowIndex, int columnIndex) { 565 if (logList != null) { 566 String text = logList.get(rowIndex).toString(); 567 568 switch (columnIndex) { 569 case 0: 570 return simpleDateFormat.format(logList.get(rowIndex).time); 571 case 1: 572 if (logList.get(rowIndex).getSource() != null) { 573 if (text != null) { 574 return logList.get(rowIndex).getSource() + " = " + text; 575 } else { 576 return logList.get(rowIndex).getSource() + " is not defined."; 577 } 578 } else { 579 return text; 580 } 581 case 2: 582 return text == null ? 0 : text.length(); 583 } 584 } 585 return null; 586 } 587 } 588 589 public static class LogRecord { 590 591 private Object message; 592 593 private int logLevel; 594 595 private Date time; 596 597 private String type; 598 599 private String source; 600 601 public LogRecord(int logLevel, Object o) { 602 if (o instanceof Map) { 603 Map map = (Map) o; 604 message = new HashMap(); 605 ((Map) message).putAll(map); 606 607 } else if (o instanceof AELogRecord) { 608 type = ((AELogRecord) o).getType(); 609 message = ((AELogRecord) o).getMessage(); 610 611 if (message instanceof Map) { 612 Map map = (Map) message; 613 message = new HashMap(); 614 ((Map) message).putAll(map); 615 } 616 617 source = ((AELogRecord) o).getVarName(); 618 } else { 619 message = o; 620 } 621 this.logLevel = logLevel; 622 this.time = new Date(); 623 } 624 625 public Object getMessage() { 626 return message; 627 } 628 629 public long getTime() { 630 return time.getTime(); 631 } 632 633 public int getLogLevel() { 634 return logLevel; 635 } 636 637 public String getType() { 638 return type; 639 } 640 641 @Override 642 public String toString() { 643 if (message instanceof byte[]) { 644 return new String((byte[]) message); 645 } 646 return ObjectUtils.toString(message, null); 647 } 648 649 public String getSource() { 650 return source; 651 } 652 653 public void setSource(String source) { 654 this.source = source; 655 } 656 657 public String getText() { 658 return ObjectUtils.toString(message); 659 } 660 661 } 662 663 @Override 664 public boolean isFilterEnabled() { 665 return log.isFilterEnabled(); 666 } 667 668 @Override 669 public String filter(Object message) { 670 return log.filter(message); 671 } 672 673}