// This file is part of PleoCommand: // Interactively control Pleo with psychobiological parameters // // Copyright (C) 2010 Oliver Hoffmann - Hoffmann_Oliver@gmx.de // // This program 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 2 // of the License, or (at your option) any later version. // // This program 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, write to the Free Software // Foundation, Inc., 51 Franklin Street, Boston, USA. package pleocmd.itfc.gui; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.EventQueue; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.swing.AbstractButton; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSeparator; import javax.swing.JTextArea; import javax.swing.ScrollPaneConstants; import pleocmd.Log; import pleocmd.cfg.ConfigCollection; import pleocmd.cfg.Configuration; import pleocmd.cfg.ConfigurationInterface; import pleocmd.cfg.Group; import pleocmd.exc.ConfigurationException; import pleocmd.itfc.gui.Layouter.Button; public final class ErrorDialog extends JDialog implements ConfigurationInterface { private static final long serialVersionUID = -8104196615240425295L; private static ErrorDialog errorDialog; private final Layouter layErrorPanel; private final JScrollPane spErrorPanel; private boolean canDisposeIfHidden; private int errorCount; private final Map<AbstractButton, String> map = new HashMap<AbstractButton, String>(); private boolean ignoreChange; private final ConfigCollection<String> cfgSuppressed = new ConfigCollection<String>( "Suppressed", ConfigCollection.Type.Set) { @Override protected String createItem(final String itemAsString) throws ConfigurationException { return itemAsString; } }; private final Map<String, MessageCount> messageCount; private ErrorDialog() { errorDialog = this; messageCount = new HashMap<String, MessageCount>(); setTitle("Error"); setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); addWindowListener(new WindowAdapter() { @Override public void windowClosing(final WindowEvent e) { close(); } }); // Add components final Layouter lay = new Layouter(this); final JPanel panel = new JPanel(); layErrorPanel = new Layouter(panel); layErrorPanel.nextComponentsAlwaysOnLastLine(); layErrorPanel.addVerticalSpacer(); spErrorPanel = new JScrollPane(panel, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); lay.addWholeLine(spErrorPanel, true); spErrorPanel.setBorder(null); lay.addButton(Button.Help, Layouter.help(this, getClass().getSimpleName())); lay.addSpacer(); lay.addButton("Reset", "edit-clear", "Clear the list of suppressed error messages", new Runnable() { @Override public void run() { reset(); } }); lay.addSpacer(); final JButton btn = lay.addButton(Button.Ok, new Runnable() { @Override public void run() { close(); } }); getRootPane().setDefaultButton(btn); btn.requestFocusInWindow(); setAlwaysOnTop(true); pack(); try { Configuration.getMain().registerConfigurableObject(this, getClass().getSimpleName()); } catch (final ConfigurationException e) { Log.error(e); } } protected static ErrorDialog the() { if (errorDialog == null) new ErrorDialog(); return errorDialog; } public static void show(final Log log) { EventQueue.invokeLater(new Runnable() { @Override public void run() { the().showLog(log); } }); } protected void showLog(final Log log) { final String caller = log.getCaller().toString(); if (cfgSuppressed.contains(caller)) return; MessageCount mc = messageCount.get(caller); if (mc == null) { mc = new MessageCount(); messageCount.put(caller, mc); } if (mc.inc() > 5) { mc.getMostRecent().setVisible(true); mc.getMostRecent().setText( String.format("%d more from the same caller", mc.getCount() - 5)); resizeDialog(); return; } final JLabel lblT; final JLabel lblC; final JLabel lblS; final JTextArea lblM; final JCheckBox cbS; if (errorCount > 0) layErrorPanel.addWholeLine(new JSeparator(), false); layErrorPanel.add(lblT = new JLabel(log.getFormattedTime()), false); layErrorPanel.add(lblC = new JLabel(log.getFormattedCaller()), false); layErrorPanel.addSpacer(); layErrorPanel.add(cbS = new JCheckBox("Suppress"), false); layErrorPanel.newLine(); layErrorPanel.addWholeLine(lblM = new JTextArea(log.getMsg()), false); layErrorPanel.addWholeLine(lblS = new JLabel(""), false); lblM.setLineWrap(true); lblM.setWrapStyleWord(true); lblM.setEditable(false); lblM.setOpaque(false); lblM.setForeground(Color.RED); lblS.setVisible(false); map.put(cbS, caller); mc.setMostRecent(lblS); cbS.addActionListener(new ActionListener() { @Override public void actionPerformed(final ActionEvent e) { final boolean sel = cbS.isSelected(); lblT.setForeground(sel ? Color.GRAY : cbS.getForeground()); lblC.setForeground(sel ? Color.GRAY : cbS.getForeground()); lblM.setForeground(sel ? Color.GRAY : Color.RED); lblS.setForeground(sel ? Color.GRAY : cbS.getForeground()); try { changeSuppress(caller, sel); } catch (final ConfigurationException exc) { Log.error(exc); } } }); ++errorCount; resizeDialog(); } private void resizeDialog() { pack(); final Dimension pref = getPreferredSize(); if (errorCount > 5) // only adapt width but keep height if already 5 // messages displayed pref.height = getHeight(); if (spErrorPanel.getVerticalScrollBar().isVisible()) pref.width += spErrorPanel.getVerticalScrollBar().getWidth(); setSize(pref); setLocationRelativeTo(null); if (!isVisible()) setVisible(true); } protected void changeSuppress(final String caller, final boolean add) throws ConfigurationException { if (ignoreChange) return; if (add) cfgSuppressed.addContent(caller); else cfgSuppressed.removeContent(caller); ignoreChange = true; try { final Component[] comps = layErrorPanel.getContainer() .getComponents(); for (final Component comp : comps) if (comp instanceof AbstractButton && map.get(comp).equals(caller) && ((AbstractButton) comp).isSelected() ^ add) ((AbstractButton) comp).doClick(); } finally { ignoreChange = false; } } protected void reset() { cfgSuppressed.clearContent(); ignoreChange = true; try { final Component[] comps = layErrorPanel.getContainer() .getComponents(); for (final Component comp : comps) if (comp instanceof AbstractButton && ((AbstractButton) comp).isSelected()) ((AbstractButton) comp).doClick(); } finally { ignoreChange = false; } } protected void close() { errorCount = 0; map.clear(); messageCount.clear(); layErrorPanel.clear(); layErrorPanel.nextComponentsAlwaysOnLastLine(); layErrorPanel.addVerticalSpacer(); pack(); if (canDisposeIfHidden) dispose(); else setVisible(false); } protected static boolean hasVisibleDialog() { return errorDialog != null && errorDialog.isVisible(); } protected static void canDisposeIfHidden() { if (errorDialog != null) { errorDialog.canDisposeIfHidden = true; if (!errorDialog.isVisible()) errorDialog.dispose(); } } @Override public Group getSkeleton(final String groupName) { return new Group(groupName).add(cfgSuppressed); } @Override public void configurationAboutToBeChanged() { // nothing to do } @Override public void configurationRead() { // nothing to do } @Override public void configurationChanged(final Group group) { // nothing to do } @Override public List<Group> configurationWriteback() { return Configuration.asList(getSkeleton(getClass().getSimpleName())); } protected static final class MessageCount { private int count; private JLabel mostRecent; public int inc() { return ++count; } public int getCount() { return count; } public JLabel getMostRecent() { return mostRecent; } public void setMostRecent(final JLabel mostRecent) { this.mostRecent = mostRecent; } } }