/* * This file is part of muCommander, http://www.mucommander.com * Copyright (C) 2002-2016 Maxence Bernard * * muCommander is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * muCommander is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.mucommander.ui.dialog.debug; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import javax.swing.DefaultListCellRenderer; import javax.swing.DefaultListModel; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.ListCellRenderer; import javax.swing.ListSelectionModel; import javax.swing.SwingUtilities; import com.mucommander.text.Translator; import com.mucommander.ui.action.ActionProperties; import com.mucommander.ui.action.impl.RefreshAction; import com.mucommander.ui.action.impl.ShowDebugConsoleAction; import com.mucommander.ui.dialog.FocusDialog; import com.mucommander.ui.main.MainFrame; import com.mucommander.utils.MuLogging; import com.mucommander.utils.MuLogging.LogLevel; /** * This dialog shows the last log messages collected by {@link DebugConsoleAppender} and allows them to be copied * to the clipboard. It also makes it possible to change the log level, the level combo box being preset to the * level returned by {@link MuLogging#getLogLevel()}. * * @see ShowDebugConsoleAction * @see DebugConsoleAppender * @see MuLogging#setLogLevel(LogLevel) * @author Maxence Bernard */ public class DebugConsoleDialog extends FocusDialog implements ActionListener, ItemListener { /** Displays log events, and allows to copy their values to the clipboard */ private JList loggingEventsList; /** Allows the log level to be changed */ private JComboBox levelComboBox; /** Closes the debug console when pressed */ private JButton closeButton; /** Refreshes the list with the latest log records when pressed */ private JButton refreshButton; // Dialog size constraints private final static Dimension MINIMUM_DIALOG_DIMENSION = new Dimension(600,400); // Dialog width should not exceed 360, height is not an issue (always the same) private final static Dimension MAXIMUM_DIALOG_DIMENSION = new Dimension(700,500); /** * Creates a new {@link DebugConsoleDialog} using the given {@link MainFrame} as a parent. * * @param mainFrame the {@link MainFrame} to use as a parent */ public DebugConsoleDialog(MainFrame mainFrame) { super(mainFrame, ActionProperties.getActionLabel(ShowDebugConsoleAction.Descriptor.ACTION_ID), mainFrame); Container contentPane = getContentPane(); loggingEventsList = new JList(); // Autoscroll when dragged loggingEventsList.setAutoscrolls(true); loggingEventsList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); loggingEventsList.setCellRenderer(new DebugListCellRenderer()); refreshLogRecords(); JScrollPane scrollPane = new JScrollPane(loggingEventsList, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); contentPane.add(scrollPane, BorderLayout.CENTER); JPanel southPanel = new JPanel(new BorderLayout()); southPanel.add(createComboPanel(), BorderLayout.WEST); JPanel buttonPanel = new JPanel(new FlowLayout()); refreshButton = new JButton(Translator.get(new RefreshAction.Descriptor().getLabel())); refreshButton.addActionListener(this); buttonPanel.add(refreshButton); closeButton = new JButton(Translator.get("close")); closeButton.addActionListener(this); buttonPanel.add(closeButton); southPanel.add(buttonPanel, BorderLayout.EAST); contentPane.add(southPanel, BorderLayout.SOUTH); setInitialFocusComponent(closeButton); setMinimumSize(MINIMUM_DIALOG_DIMENSION); setMaximumSize(MAXIMUM_DIALOG_DIMENSION); setInitialFocusComponent(closeButton); } /** * Creates and returns a panel containing the level combo box and a leading localized label describing it. * * @return a panel containing the level combo box and a leading localized label describing it */ private JPanel createComboPanel() { JPanel comboPanel = new JPanel(new FlowLayout()); comboPanel.add(new JLabel(Translator.get("debug_console_dialog.level")+":")); LogLevel logLevel = MuLogging.getLogLevel(); levelComboBox = new JComboBox(); for(LogLevel level:LogLevel.values()) levelComboBox.addItem(level); levelComboBox.setSelectedItem(logLevel); levelComboBox.addItemListener(this); comboPanel.add(levelComboBox); return comboPanel; } /** * Refreshes the JList with the log records contained by {@link DebugConsoleAppender}. */ private void refreshLogRecords() { DefaultListModel listModel = new DefaultListModel(); DebugConsoleAppender handler = MuLogging.getDebugConsoleAppender(); final LoggingEvent[] records = handler.getLogRecords(); final LogLevel currentLogLevel = MuLogging.getLogLevel(); for (LoggingEvent record : records) { if (record.isLevelEqualOrHigherThan(currentLogLevel)) listModel.addElement(record); } loggingEventsList.setModel(listModel); SwingUtilities.invokeLater(new Runnable() { public void run() { loggingEventsList.ensureIndexIsVisible(records.length-1); } }); } /** * Changes the log level to the selected combo box value. */ private void updateLogLevel() { LogLevel newLevel = (LogLevel) levelComboBox.getSelectedItem(); MuLogging.setLogLevel(newLevel); } /////////////////////////////////// // ActionListener implementation // /////////////////////////////////// public void actionPerformed(ActionEvent e) { Object source = e.getSource(); if(source==refreshButton) { refreshLogRecords(); } else if(source==closeButton) { dispose(); } } ///////////////////////////////// // ItemListener implementation // ///////////////////////////////// public void itemStateChanged(ItemEvent e) { // Refresh the log records displayed in the JList whenever the selected level has been changed. int selectedIndex = levelComboBox.getSelectedIndex(); if(selectedIndex!=-1) { updateLogLevel(); refreshLogRecords(); } } /////////////////// // Inner classes // /////////////////// /** * Custom {@link ListCellRenderer} that renders {@link LoggingEvent} instances. */ private class DebugListCellRenderer extends DefaultListCellRenderer { @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { if(value==null) return null; // TODO: line-wrap log items when the text is too long to fit on a single line // A single-column JTable may be the easiest way to go, see: // http://javaspecialists.co.za/archive/newsletter.do?issue=106&locale=en_US // http://forums.sun.com/thread.jspa?threadID=702740&start=0&tstart=0 // Using a JTextArea with line-wrapping enabled does not work as a JList has by design a fixed height // for cells JLabel label = (JLabel)super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); // Change the label's foreground color to match the level of the log record if(!isSelected) { LogLevel level = ((LoggingEvent)value).getLevel(); Color color; if(level.equals(LogLevel.SEVERE)) color = Color.RED; else if(level.equals(LogLevel.WARNING)) color = new Color(255, 100, 0); // Dark orange else if(level.equals(LogLevel.CONFIG)) color = Color.BLUE; else if(level.equals(LogLevel.INFO)) color = Color.BLACK; else if(level.equals(LogLevel.FINE)) color = Color.DARK_GRAY; else color = new Color(110, 110, 110); // Between Color.GRAY and Color.DARK_GRAY label.setForeground(color); } // TODO: remove this when line-wrapping has been implemented // If component's preferred width is larger than the list's width then the component is not entirely // visible. In that case, we set a tooltip text that will display the whole text when mouse is over the // component if (loggingEventsList.getVisibleRect().getWidth() < label.getPreferredSize().getWidth()) label.setToolTipText(label.getText()); // Have to set it to null because of the rubber-stamp rendering scheme (last value is kept) else label.setToolTipText(null); return label; } } }