View Javadoc

    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements.  See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache License, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License.  You may obtain a copy of the License at
    8    *
    9    *      http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   */
    17
    18  package org.apache.log4j.chainsaw;
    19
    20  import java.awt.BorderLayout;
    21  import java.awt.Component;
    22  import java.awt.Container;
    23  import java.awt.Dimension;
    24  import java.awt.EventQueue;
    25  import java.awt.Frame;
    26  import java.awt.Point;
    27  import java.awt.Toolkit;
    28  import java.awt.event.ActionEvent;
    29  import java.awt.event.ActionListener;
    30  import java.awt.event.InputEvent;
    31  import java.awt.event.KeyEvent;
    32  import java.awt.event.MouseAdapter;
    33  import java.awt.event.MouseEvent;
    34  import java.awt.event.WindowAdapter;
    35  import java.awt.event.WindowEvent;
    36  import java.beans.PropertyChangeEvent;
    37  import java.beans.PropertyChangeListener;
    38  import java.io.File;
    39  import java.io.IOException;
    40  import java.lang.Thread.UncaughtExceptionHandler;
    41  import java.lang.reflect.Method;
    42  import java.net.MalformedURLException;
    43  import java.net.URISyntaxException;
    44  import java.net.URL;
    45  import java.security.AllPermission;
    46  import java.security.CodeSource;
    47  import java.security.PermissionCollection;
    48  import java.security.Permissions;
    49  import java.security.Policy;
    50  import java.util.ArrayList;
    51  import java.util.Enumeration;
    52  import java.util.HashMap;
    53  import java.util.Iterator;
    54  import java.util.List;
    55  import java.util.Locale;
    56  import java.util.Map;
    57  import java.util.Set;
    58
    59  import javax.swing.AbstractAction;
    60  import javax.swing.Action;
    61  import javax.swing.BorderFactory;
    62  import javax.swing.Box;
    63  import javax.swing.Icon;
    64  import javax.swing.ImageIcon;
    65  import javax.swing.JButton;
    66  import javax.swing.JComponent;
    67  import javax.swing.JDialog;
    68  import javax.swing.JEditorPane;
    69  import javax.swing.JFrame;
    70  import javax.swing.JOptionPane;
    71  import javax.swing.JPanel;
    72  import javax.swing.JPopupMenu;
    73  import javax.swing.JScrollPane;
    74  import javax.swing.JSplitPane;
    75  import javax.swing.JToolBar;
    76  import javax.swing.JWindow;
    77  import javax.swing.KeyStroke;
    78  import javax.swing.SwingConstants;
    79  import javax.swing.SwingUtilities;
    80  import javax.swing.ToolTipManager;
    81  import javax.swing.UIManager;
    82  import javax.swing.WindowConstants;
    83  import javax.swing.event.ChangeEvent;
    84  import javax.swing.event.ChangeListener;
    85  import javax.swing.event.EventListenerList;
    86  import javax.swing.event.HyperlinkEvent;
    87  import javax.swing.event.HyperlinkListener;
    88
    89  import org.apache.log4j.Appender;
    90  import org.apache.log4j.AppenderSkeleton;
    91  import org.apache.log4j.Level;
    92  import org.apache.log4j.LogManager;
    93  import org.apache.log4j.Logger;
    94  import org.apache.log4j.LoggerRepositoryExImpl;
    95  import org.apache.log4j.chainsaw.color.RuleColorizer;
    96  import org.apache.log4j.chainsaw.dnd.FileDnDTarget;
    97  import org.apache.log4j.chainsaw.help.HelpManager;
    98  import org.apache.log4j.chainsaw.help.Tutorial;
    99  import org.apache.log4j.chainsaw.helper.SwingHelper;
    100 import org.apache.log4j.chainsaw.icons.ChainsawIcons;
    101 import org.apache.log4j.chainsaw.icons.LineIconFactory;
    102 import org.apache.log4j.chainsaw.messages.MessageCenter;
    103 import org.apache.log4j.chainsaw.osx.OSXIntegration;
    104 import org.apache.log4j.chainsaw.plugins.PluginClassLoaderFactory;
    105 import org.apache.log4j.chainsaw.prefs.LoadSettingsEvent;
    106 import org.apache.log4j.chainsaw.prefs.MRUFileListPreferenceSaver;
    107 import org.apache.log4j.chainsaw.prefs.SaveSettingsEvent;
    108 import org.apache.log4j.chainsaw.prefs.SettingsListener;
    109 import org.apache.log4j.chainsaw.prefs.SettingsManager;
    110 import org.apache.log4j.chainsaw.receivers.ReceiversHelper;
    111 import org.apache.log4j.chainsaw.receivers.ReceiversPanel;
    112 import org.apache.log4j.chainsaw.vfs.VFSLogFilePatternReceiver;
    113 import org.apache.log4j.net.SocketNodeEventListener;
    114 import org.apache.log4j.plugins.Plugin;
    115 import org.apache.log4j.plugins.PluginEvent;
    116 import org.apache.log4j.plugins.PluginListener;
    117 import org.apache.log4j.plugins.PluginRegistry;
    118 import org.apache.log4j.plugins.Receiver;
    119 import org.apache.log4j.rewrite.PropertyRewritePolicy;
    120 import org.apache.log4j.rewrite.RewriteAppender;
    121 import org.apache.log4j.rule.ExpressionRule;
    122 import org.apache.log4j.rule.Rule;
    123 import org.apache.log4j.spi.Decoder;
    124 import org.apache.log4j.spi.LoggerRepository;
    125 import org.apache.log4j.spi.LoggerRepositoryEx;
    126 import org.apache.log4j.spi.LoggingEvent;
    127 import org.apache.log4j.spi.RepositorySelector;
    128 import org.apache.log4j.xml.DOMConfigurator;
    129 import org.apache.log4j.xml.XMLDecoder;
    130
    131
    132 /**
    133  * The main entry point for Chainsaw, this class represents the first frame
    134  * that is used to display a Welcome panel, and any other panels that are
    135  * generated because Logging Events are streamed via a Receiver, or other
    136  * mechanism.
    137  *
    138  * NOTE: Some of Chainsaw's application initialization should be performed prior
    139  * to activating receivers and the logging framework used to perform self-logging.
    140  *
    141  * DELAY as much as possible the logging framework initialization process,
    142  * currently initialized by the creation of a ChainsawAppenderHandler.
    143  *
    144  * @author Scott Deboy &lt;sdeboy@apache.org&gt;
    145  * @author Paul Smith  &lt;psmith@apache.org&gt;
    146  *
    147  */
    148 public class LogUI extends JFrame implements ChainsawViewer, SettingsListener {
    149   private static final String MAIN_WINDOW_HEIGHT = "main.window.height";
    150   private static final String MAIN_WINDOW_WIDTH = "main.window.width";
    151   private static final String MAIN_WINDOW_Y = "main.window.y";
    152   private static final String MAIN_WINDOW_X = "main.window.x";
    153   private static ChainsawSplash splash;
    154   private static final double DEFAULT_MAIN_RECEIVER_SPLIT_LOCATION = 0.85d;
    155   private final JFrame preferencesFrame = new JFrame();
    156   private boolean noReceiversDefined;
    157   private ReceiversPanel receiversPanel;
    158   private ChainsawTabbedPane tabbedPane;
    159   private JToolBar toolbar;
    160   private ChainsawStatusBar statusBar;
    161   private  ApplicationPreferenceModel applicationPreferenceModel;
    162   private ApplicationPreferenceModelPanel applicationPreferenceModelPanel;
    163   private final Map tableModelMap = new HashMap();
    164   private final Map tableMap = new HashMap();
    165   private final List filterableColumns = new ArrayList();
    166   private final Map panelMap = new HashMap();
    167   ChainsawAppenderHandler handler;
    168   private ChainsawToolBarAndMenus tbms;
    169   private ChainsawAbout aboutBox;
    170   private final SettingsManager sm = SettingsManager.getInstance();
    171   private final JFrame tutorialFrame = new JFrame("Chainsaw Tutorial");
    172   private JSplitPane mainReceiverSplitPane;
    173   private double lastMainReceiverSplitLocation = DEFAULT_MAIN_RECEIVER_SPLIT_LOCATION;
    174   private final List identifierPanels = new ArrayList();
    175   private int dividerSize;
    176   private int cyclicBufferSize;
    177   private static Logger logger;
    178   private static String configurationURLAppArg;
    179
    180   /**
    181    * Set to true, if and only if the GUI has completed it's full
    182    * initialization. Any logging events that come in must wait until this is
    183    * true, and if it is false, should wait on the initializationLock object
    184    * until notified.
    185    */
    186   private boolean isGUIFullyInitialized = false;
    187   private Object initializationLock = new Object();
    188
    189   /**
    190    * The shutdownAction is called when the user requests to exit Chainsaw, and
    191    * by default this exits the VM, but a developer may replace this action with
    192    * something that better suits their needs
    193    */
    194   private Action shutdownAction = null;
    195
    196   /**
    197    * Clients can register a ShutdownListener to be notified when the user has
    198    * requested Chainsaw to exit.
    199    */
    200   private EventListenerList shutdownListenerList = new EventListenerList();
    201   private WelcomePanel welcomePanel;
    202
    203   private static final Object repositorySelectorGuard = new Object();
    204   private static final LoggerRepositoryExImpl repositoryExImpl = new LoggerRepositoryExImpl(LogManager.getLoggerRepository());
    205
    206   private PluginRegistry pluginRegistry;
    207   //map of tab names to rulecolorizers
    208   private Map allColorizers = new HashMap();
    209   private ReceiverConfigurationPanel receiverConfigurationPanel = new ReceiverConfigurationPanel();
    210
    211   /**
    212    * Constructor which builds up all the visual elements of the frame including
    213    * the Menu bar
    214    */
    215   public LogUI() {
    216     super("Chainsaw");
    217     setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
    218
    219     if (ChainsawIcons.WINDOW_ICON != null) {
    220       setIconImage(new ImageIcon(ChainsawIcons.WINDOW_ICON).getImage());
    221     }
    222   }
    223
    224   private static final void showSplash(Frame owner) {
    225     splash = new ChainsawSplash(owner);
    226     SwingHelper.centerOnScreen(splash);
    227     splash.setVisible(true);
    228   }
    229
    230   private static final void removeSplash() {
    231     if (splash != null) {
    232       splash.setVisible(false);
    233       splash.dispose();
    234     }
    235   }
    236
    237   /**
    238    * Registers a ShutdownListener with this calss so that it can be notified
    239    * when the user has requested that Chainsaw exit.
    240    *
    241    * @param l
    242    */
    243   public void addShutdownListener(ShutdownListener l) {
    244     shutdownListenerList.add(ShutdownListener.class, l);
    245   }
    246
    247   /**
    248    * Removes the registered ShutdownListener so that the listener will not be
    249    * notified on a shutdown.
    250    *
    251    * @param l
    252    */
    253   public void removeShutdownListener(ShutdownListener l) {
    254     shutdownListenerList.remove(ShutdownListener.class, l);
    255   }
    256
    257   /**
    258    * Starts Chainsaw by attaching a new instance to the Log4J main root Logger
    259    * via a ChainsawAppender, and activates itself
    260    *
    261    * @param args
    262    */
    263   public static void main(String[] args) {
    264       if (args.length > 0) {
    265           configurationURLAppArg = args[0];
    266       }
    267
    268       if(OSXIntegration.IS_OSX) {
    269           System.setProperty("apple.laf.useScreenMenuBar", "true");
    270       }
    271
    272
    273     LogManager.setRepositorySelector(new RepositorySelector() {
    274
    275         public LoggerRepository getLoggerRepository() {
    276             return repositoryExImpl;
    277         }}, repositorySelectorGuard);
    278
    279     final ApplicationPreferenceModel model = new ApplicationPreferenceModel();
    280
    281     SettingsManager.getInstance().configure(new ApplicationPreferenceModelSaver(model));
    282     //if a configuration URL param was provided, set the configuration URL field to null
    283     if (configurationURLAppArg != null) {
    284         model.setBypassConfigurationURL(configurationURLAppArg);
    285     }
    286
    287     EventQueue.invokeLater(new Runnable()
    288     {
    289         public void run()
    290         {
    291           String lookAndFeelClassName = model.getLookAndFeelClassName();
    292           if (lookAndFeelClassName == null || lookAndFeelClassName.trim().equals("")) {
    293               String osName = System.getProperty("os.name");
    294               if (osName.toLowerCase(Locale.ENGLISH).startsWith("mac")) {
    295                   //no need to assign look and feel
    296               } else if (osName.toLowerCase(Locale.ENGLISH).startsWith("windows")) {
    297                   lookAndFeelClassName = "com.sun.java.swing.plaf.windows.WindowsLookAndFeel";
    298                   model.setLookAndFeelClassName(lookAndFeelClassName);
    299               } else if (osName.toLowerCase(Locale.ENGLISH).startsWith("linux")) {
    300                   lookAndFeelClassName = "com.sun.java.swing.plaf.gtk.GTKLookAndFeel";
    301                   model.setLookAndFeelClassName(lookAndFeelClassName);
    302               }
    303           }
    304
    305           if (lookAndFeelClassName != null && !(lookAndFeelClassName.trim().equals(""))) {
    306             loadLookAndFeelUsingPluginClassLoader(lookAndFeelClassName);
    307           }
    308           createChainsawGUI(model, null);
    309         }
    310     });
    311   }
    312
    313   /**
    314    * Creates, activates, and then shows the Chainsaw GUI, optionally showing
    315    * the splash screen, and using the passed shutdown action when the user
    316    * requests to exit the application (if null, then Chainsaw will exit the vm)
    317    *
    318    * @param model
    319    * @param newShutdownAction
    320    *                    DOCUMENT ME!
    321    */
    322   public static void createChainsawGUI(
    323     ApplicationPreferenceModel model, Action newShutdownAction) {
    324
    325     if (model.isOkToRemoveSecurityManager()) {
    326 			MessageCenter
    327 					.getInstance()
    328 					.addMessage(
    329 							"User has authorised removal of Java Security Manager via preferences");
    330 			System.setSecurityManager(null);
    331             // this SHOULD set the Policy/Permission stuff for any
    332             // code loaded from our custom classloader.
    333             // crossing fingers...
    334 			Policy.setPolicy(new Policy() {
    335
    336 				public void refresh() {
    337 				}
    338
    339 				public PermissionCollection getPermissions(CodeSource codesource) {
    340 					Permissions perms = new Permissions();
    341 					perms.add(new AllPermission());
    342 					return (perms);
    343 				}
    344 			});
    345     }
    346
    347     final LogUI logUI = new LogUI();
    348     logUI.applicationPreferenceModel = model;
    349
    350     if (model.isShowSplash()) {
    351       showSplash(logUI);
    352     }
    353     logUI.cyclicBufferSize = model.getCyclicBufferSize();
    354     logUI.pluginRegistry = repositoryExImpl.getPluginRegistry();
    355
    356     logUI.handler = new ChainsawAppenderHandler();
    357     logUI.handler.addEventBatchListener(logUI.new NewTabEventBatchReceiver());
    358
    359     /**
    360      * TODO until we work out how JoranConfigurator might be able to have
    361      * configurable class loader, if at all.  For now we temporarily replace the
    362      * TCCL so that Plugins that need access to resources in
    363      * the Plugins directory can find them (this is particularly
    364      * important for the Web start version of Chainsaw
    365      */
    366     //configuration initialized here
    367     logUI.ensureChainsawAppenderHandlerAdded();
    368     logger = LogManager.getLogger(LogUI.class);
    369
    370     //set hostname, application and group properties which will cause Chainsaw and other apache-generated
    371     //logging events to route (by default) to a tab named 'chainsaw-log'
    372     PropertyRewritePolicy policy = new PropertyRewritePolicy();
    373     policy.setProperties("hostname=chainsaw,application=log,group=chainsaw");
    374
    375     RewriteAppender rewriteAppender = new RewriteAppender();
    376     rewriteAppender.setRewritePolicy(policy);
    377
    378     Enumeration appenders = Logger.getLogger("org.apache").getAllAppenders();
    379     if (!appenders.hasMoreElements()) {
    380     	appenders = Logger.getRootLogger().getAllAppenders();
    381     }
    382     while (appenders.hasMoreElements()) {
    383     	Appender nextAppender = (Appender)appenders.nextElement();
    384     	rewriteAppender.addAppender(nextAppender);
    385     }
    386     Logger.getLogger("org.apache").removeAllAppenders();
    387     Logger.getLogger("org.apache").addAppender(rewriteAppender);
    388     Logger.getLogger("org.apache").setAdditivity(false);
    389
    390     //commons-vfs uses httpclient for http filesystem support, route this to the chainsaw-log tab as well
    391     appenders = Logger.getLogger("httpclient").getAllAppenders();
    392     if (!appenders.hasMoreElements()) {
    393         appenders = Logger.getRootLogger().getAllAppenders();
    394     }
    395     while (appenders.hasMoreElements()) {
    396         Appender nextAppender = (Appender)appenders.nextElement();
    397         rewriteAppender.addAppender(nextAppender);
    398     }
    399     Logger.getLogger("httpclient").removeAllAppenders();
    400     Logger.getLogger("httpclient").addAppender(rewriteAppender);
    401     Logger.getLogger("httpclient").setAdditivity(false);
    402
    403     //set the commons.vfs.cache logger to info, since it can contain password information
    404     Logger.getLogger("org.apache.commons.vfs.cache").setLevel(Level.INFO);
    405
    406     Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() {
    407 		public void uncaughtException(Thread t, Throwable e) {
    408             e.printStackTrace();
    409 			logger.error("Uncaught exception in thread " + t, e);
    410 		}
    411     });
    412
    413     String config = configurationURLAppArg;
    414     if (config != null) {
    415         logger.info("Command-line configuration arg provided (overriding auto-configuration URL) - using: " + config);
    416     } else {
    417         config = model.getConfigurationURL();
    418     }
    419
    420     if (config != null && (!config.trim().equals(""))) {
    421       config = config.trim();
    422       try {
    423         URL configURL = new URL(config);
    424         logger.info("Using '" + config + "' for auto-configuration");
    425         logUI.loadConfigurationUsingPluginClassLoader(configURL);
    426       } catch(MalformedURLException e) {
    427         logger.error("Initial configuration - failed to convert config string to url", e);
    428       } catch (IOException e) {
    429           logger.error("Unable to access auto-configuration URL: " + config);
    430       }
    431     }
    432
    433       //register a listener to load the configuration when it changes (avoid having to restart Chainsaw when applying a new configuration)
    434     //this doesn't remove receivers from receivers panel, it just triggers DOMConfigurator.configure.
    435 	model.addPropertyChangeListener("configurationURL", new PropertyChangeListener() {
    436         public void propertyChange(PropertyChangeEvent evt) {
    437             String newConfiguration = evt.getNewValue().toString();
    438             if (newConfiguration != null && !(newConfiguration.trim().equals(""))) {
    439                 newConfiguration = newConfiguration.trim();
    440                 try {
    441                     logger.info("loading updated configuration: " + newConfiguration);
    442                     URL newConfigurationURL = new URL(newConfiguration);
    443                     File file = new File(newConfigurationURL.toURI());
    444                     if (file.exists()) {
    445                         logUI.loadConfigurationUsingPluginClassLoader(newConfigurationURL);
    446                     } else {
    447                         logger.info("Updated configuration but file does not exist");
    448                     }
    449                 } catch (MalformedURLException e) {
    450                     logger.error("Updated configuration - failed to convert config string to URL", e);
    451                 }
    452                 catch (URISyntaxException e)
    453                 {
    454                     logger.error("Updated configuration - failed to convert config string to URL", e);
    455                 }
    456             }
    457         }});
    458
    459     LogManager.getRootLogger().setLevel(Level.TRACE);
    460     EventQueue.invokeLater(new Runnable() {
    461         public void run() {
    462             logUI.activateViewer();
    463         }
    464     });
    465
    466     logger.info("SecurityManager is now: " + System.getSecurityManager());
    467
    468     if (newShutdownAction != null) {
    469       logUI.setShutdownAction(newShutdownAction);
    470     } else {
    471       logUI.setShutdownAction(
    472         new AbstractAction() {
    473           public void actionPerformed(ActionEvent e) {
    474             System.exit(0);
    475           }
    476         });
    477     }
    478   }
    479
    480   /**
    481    * Allow Chainsaw v2 to be ran in-process (configured as a ChainsawAppender)
    482    * NOTE: Closing Chainsaw will NOT stop the application generating the events.
    483    * @param appender
    484    *
    485    */
    486   public void activateViewer(ChainsawAppender appender) {
    487
    488     if(OSXIntegration.IS_OSX) {
    489         System.setProperty("apple.laf.useScreenMenuBar", "true");
    490     }
    491
    492     LogManager.setRepositorySelector(new RepositorySelector() {
    493
    494       public LoggerRepository getLoggerRepository() {
    495           return repositoryExImpl;
    496       }}, repositorySelectorGuard);
    497
    498     //if Chainsaw is launched as an appender, ensure the root logger level is TRACE
    499     LogManager.getRootLogger().setLevel(Level.TRACE);
    500
    501     final ApplicationPreferenceModel model = new ApplicationPreferenceModel();
    502     SettingsManager.getInstance().configure(new ApplicationPreferenceModelSaver(model));
    503
    504     cyclicBufferSize = model.getCyclicBufferSize();
    505
    506     handler = new ChainsawAppenderHandler(appender);
    507     handler.addEventBatchListener(new NewTabEventBatchReceiver());
    508
    509     logger = LogManager.getLogger(LogUI.class);
    510
    511     setShutdownAction(
    512         new AbstractAction() {
    513           public void actionPerformed(ActionEvent e) {
    514           }
    515         });
    516
    517     applicationPreferenceModel = new ApplicationPreferenceModel();
    518
    519     SettingsManager.getInstance().configure(new ApplicationPreferenceModelSaver(model));
    520
    521     EventQueue.invokeLater(new Runnable()
    522     {
    523         public void run()
    524         {
    525             loadLookAndFeelUsingPluginClassLoader(model.getLookAndFeelClassName());
    526             createChainsawGUI(model, null);
    527             getApplicationPreferenceModel().apply(model);
    528             activateViewer();
    529         }
    530     });
    531   }
    532
    533   /**
    534    * Initialises the menu's and toolbars, but does not actually create any of
    535    * the main panel components.
    536    *
    537    */
    538   private void initGUI() {
    539
    540     setupHelpSystem();
    541     statusBar = new ChainsawStatusBar(this);
    542     setupReceiverPanel();
    543
    544     setToolBarAndMenus(new ChainsawToolBarAndMenus(this));
    545     toolbar = getToolBarAndMenus().getToolbar();
    546     setJMenuBar(getToolBarAndMenus().getMenubar());
    547
    548     setTabbedPane(new ChainsawTabbedPane());
    549     getSettingsManager().addSettingsListener(getTabbedPane());
    550     getSettingsManager().configure(getTabbedPane());
    551
    552     /**
    553      * This adds Drag & Drop capability to Chainsaw
    554      */
    555     FileDnDTarget dnDTarget = new FileDnDTarget(tabbedPane);
    556     dnDTarget.addPropertyChangeListener("fileList", new PropertyChangeListener() {
    557
    558         public void propertyChange(PropertyChangeEvent evt) {
    559             final List fileList = (List) evt.getNewValue();
    560
    561             Thread thread = new Thread(new Runnable() {
    562
    563
    564                 public void run() {
    565                     logger.debug("Loading files: " + fileList);
    566                     for (Iterator iter = fileList.iterator(); iter.hasNext();) {
    567                         File  file = (File) iter.next();
    568                         final Decoder decoder = new XMLDecoder();
    569                         try {
    570                             getStatusBar().setMessage("Loading " + file.getAbsolutePath() + "...");
    571                             FileLoadAction.importURL(handler, decoder, file
    572                                     .getName(), file.toURI().toURL());
    573                         } catch (Exception e) {
    574                             String errorMsg = "Failed to import a file";
    575                             logger.error(errorMsg, e);
    576                             getStatusBar().setMessage(errorMsg);
    577                         }
    578                     }
    579
    580                 }});
    581
    582             thread.setPriority(Thread.MIN_PRIORITY);
    583             thread.start();
    584
    585         }});
    586
    587     applicationPreferenceModelPanel = new ApplicationPreferenceModelPanel(applicationPreferenceModel);
    588
    589     applicationPreferenceModelPanel.setOkCancelActionListener(
    590       new ActionListener() {
    591         public void actionPerformed(ActionEvent e) {
    592           preferencesFrame.setVisible(false);
    593         }
    594       });
    595       KeyStroke escape = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, false);
    596           Action closeAction = new AbstractAction() {
    597               public void actionPerformed(ActionEvent e) {
    598                 preferencesFrame.setVisible(false);
    599               }
    600           };
    601           preferencesFrame.getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(escape, "ESCAPE"); preferencesFrame.getRootPane().
    602                   getActionMap().put("ESCAPE", closeAction);
    603
    604     OSXIntegration.init(this);
    605
    606   }
    607
    608   private void initPlugins(PluginRegistry pluginRegistry) {
    609     pluginRegistry.addPluginListener(
    610       new PluginListener() {
    611         public void pluginStarted(PluginEvent e) {
    612           if (e.getPlugin() instanceof JComponent) {
    613             JComponent plugin = (JComponent) e.getPlugin();
    614             getTabbedPane().addANewTab(plugin.getName(), plugin, null, null);
    615             getPanelMap().put(plugin.getName(), plugin);
    616           }
    617         }
    618
    619         public void pluginStopped(PluginEvent e) {
    620           //TODO remove the plugin from the gui
    621         }
    622       });
    623
    624     // TODO this should all be in a config file
    625 //    ChainsawCentral cc = new ChainsawCentral();
    626 //    pluginRegistry.addPlugin(cc);
    627 //    cc.activateOptions();
    628
    629     try {
    630         Class pluginClass = Class.forName("org.apache.log4j.chainsaw.zeroconf.ZeroConfPlugin");
    631         Plugin plugin = (Plugin) pluginClass.newInstance();
    632         pluginRegistry.addPlugin(plugin);
    633         plugin.activateOptions();
    634         MessageCenter.getInstance().getLogger().info("Looks like ZeroConf is available... WooHoo!");
    635     } catch (Throwable e) {
    636         MessageCenter.getInstance().getLogger().error("Doesn't look like ZeroConf is available", e);
    637     }
    638   }
    639
    640   private void setupReceiverPanel() {
    641     receiversPanel = new ReceiversPanel();
    642     receiversPanel.addPropertyChangeListener(
    643       "visible",
    644       new PropertyChangeListener() {
    645         public void propertyChange(PropertyChangeEvent evt) {
    646           getApplicationPreferenceModel().setReceivers(
    647             ((Boolean) evt.getNewValue()).booleanValue());
    648         }
    649       });
    650   }
    651
    652   /**
    653    * Initialises the Help system and the WelcomePanel
    654    *
    655    */
    656   private void setupHelpSystem() {
    657     welcomePanel = new WelcomePanel();
    658
    659     JToolBar tb = welcomePanel.getToolbar();
    660
    661
    662     tb.add(
    663       new SmallButton(
    664         new AbstractAction("Tutorial", new ImageIcon(ChainsawIcons.HELP)) {
    665         public void actionPerformed(ActionEvent e) {
    666           setupTutorial();
    667         }
    668       }));
    669     tb.addSeparator();
    670
    671     final Action exampleConfigAction =
    672       new AbstractAction("View example Receiver configuration") {
    673         public void actionPerformed(ActionEvent e) {
    674           HelpManager.getInstance().setHelpURL(
    675             ChainsawConstants.EXAMPLE_CONFIG_URL);
    676         }
    677       };
    678
    679     exampleConfigAction.putValue(
    680       Action.SHORT_DESCRIPTION,
    681       "Displays an example Log4j configuration file with several Receivers defined.");
    682
    683     JButton exampleButton = new SmallButton(exampleConfigAction);
    684     tb.add(exampleButton);
    685
    686     tb.add(Box.createHorizontalGlue());
    687
    688     /**
    689      * Setup a listener on the HelpURL property and automatically change the WelcomePages URL
    690      * to it.
    691      */
    692     HelpManager.getInstance().addPropertyChangeListener(
    693       "helpURL",
    694       new PropertyChangeListener() {
    695         public void propertyChange(PropertyChangeEvent evt) {
    696           URL newURL = (URL) evt.getNewValue();
    697
    698           if (newURL != null) {
    699             welcomePanel.setURL(newURL);
    700             ensureWelcomePanelVisible();
    701           }
    702         }
    703       });
    704   }
    705
    706   private void ensureWelcomePanelVisible() {
    707       // ensure that the Welcome Panel is made visible
    708       if(!getTabbedPane().containsWelcomePanel()) {
    709           addWelcomePanel();
    710       }
    711       getTabbedPane().setSelectedComponent(welcomePanel);
    712   }
    713
    714   /**
    715    * Given the load event, configures the size/location of the main window etc
    716    * etc.
    717    *
    718    * @param event
    719    *                    DOCUMENT ME!
    720    */
    721   public void loadSettings(LoadSettingsEvent event) {
    722     setLocation(
    723       event.asInt(LogUI.MAIN_WINDOW_X), event.asInt(LogUI.MAIN_WINDOW_Y));
    724       int width = event.asInt(LogUI.MAIN_WINDOW_WIDTH);
    725       int height = event.asInt(LogUI.MAIN_WINDOW_HEIGHT);
    726       if (width == -1 && height == -1) {
    727           width = Toolkit.getDefaultToolkit().getScreenSize().width;
    728           height = Toolkit.getDefaultToolkit().getScreenSize().height;
    729           setSize(width, height);
    730           setExtendedState(getExtendedState() | MAXIMIZED_BOTH);
    731       } else {
    732           setSize(width, height);
    733       }
    734
    735     getToolBarAndMenus().stateChange();
    736     RuleColorizer colorizer = new RuleColorizer();
    737     colorizer.loadColorSettings(ChainsawConstants.DEFAULT_COLOR_RULE_NAME);
    738     allColorizers.put(ChainsawConstants.DEFAULT_COLOR_RULE_NAME, colorizer);
    739     if (event.getSetting("SavedConfig.logFormat") != null) {
    740       receiverConfigurationPanel.getModel().setLogFormat(event.getSetting("SavedConfig.logFormat"));
    741     }
    742   }
    743
    744   /**
    745    * Ensures the location/size of the main window is stored with the settings
    746    *
    747    * @param event
    748    *                    DOCUMENT ME!
    749    */
    750   public void saveSettings(SaveSettingsEvent event) {
    751     event.saveSetting(LogUI.MAIN_WINDOW_X, (int) getLocation().getX());
    752     event.saveSetting(LogUI.MAIN_WINDOW_Y, (int) getLocation().getY());
    753
    754     event.saveSetting(LogUI.MAIN_WINDOW_WIDTH, getWidth());
    755     event.saveSetting(LogUI.MAIN_WINDOW_HEIGHT, getHeight());
    756     RuleColorizer colorizer = (RuleColorizer) allColorizers.get(ChainsawConstants.DEFAULT_COLOR_RULE_NAME);
    757     colorizer.saveColorSettings(ChainsawConstants.DEFAULT_COLOR_RULE_NAME);
    758     if (receiverConfigurationPanel.getModel().getLogFormat() != null ) {
    759       event.saveSetting("SavedConfig.logFormat", receiverConfigurationPanel.getModel().getLogFormat());
    760     }
    761   }
    762
    763   /**
    764    * Activates itself as a viewer by configuring Size, and location of itself,
    765    * and configures the default Tabbed Pane elements with the correct layout,
    766    * table columns, and sets itself viewable.
    767    */
    768   public void activateViewer() {
    769     LoggerRepository repo = LogManager.getLoggerRepository();
    770     if (repo instanceof LoggerRepositoryEx) {
    771         this.pluginRegistry = ((LoggerRepositoryEx) repo).getPluginRegistry();
    772     }
    773     initGUI();
    774
    775     initPrefModelListeners();
    776
    777     /**
    778      * We add a simple appender to the MessageCenter logger
    779      * so that each message is displayed in the Status bar
    780      */
    781     MessageCenter.getInstance().getLogger().addAppender(
    782       new AppenderSkeleton() {
    783         protected void append(LoggingEvent event) {
    784           getStatusBar().setMessage(event.getMessage().toString());
    785         }
    786
    787         public void close() {
    788         }
    789
    790         public boolean requiresLayout() {
    791           return false;
    792         }
    793       });
    794
    795
    796
    797     initSocketConnectionListener();
    798
    799     if (pluginRegistry.getPlugins(Receiver.class).size() == 0) {
    800       noReceiversDefined = true;
    801     }
    802
    803     getFilterableColumns().add(ChainsawConstants.LEVEL_COL_NAME);
    804     getFilterableColumns().add(ChainsawConstants.LOGGER_COL_NAME);
    805     getFilterableColumns().add(ChainsawConstants.THREAD_COL_NAME);
    806     getFilterableColumns().add(ChainsawConstants.NDC_COL_NAME);
    807     getFilterableColumns().add(ChainsawConstants.PROPERTIES_COL_NAME);
    808     getFilterableColumns().add(ChainsawConstants.CLASS_COL_NAME);
    809     getFilterableColumns().add(ChainsawConstants.METHOD_COL_NAME);
    810     getFilterableColumns().add(ChainsawConstants.FILE_COL_NAME);
    811     getFilterableColumns().add(ChainsawConstants.NONE_COL_NAME);
    812
    813     JPanel panePanel = new JPanel();
    814     panePanel.setLayout(new BorderLayout(2, 2));
    815
    816     getContentPane().setLayout(new BorderLayout());
    817
    818     getTabbedPane().addChangeListener(getToolBarAndMenus());
    819     getTabbedPane().addChangeListener(new ChangeListener()
    820     {
    821         public void stateChanged(ChangeEvent e)
    822         {
    823             LogPanel thisLogPanel = getCurrentLogPanel();
    824             if (thisLogPanel != null)
    825             {
    826                 thisLogPanel.updateStatusBar();
    827             }
    828         }
    829     });
    830
    831     KeyStroke ksRight =
    832       KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask());
    833     KeyStroke ksLeft =
    834       KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask());
    835     KeyStroke ksGotoLine =
    836       KeyStroke.getKeyStroke(KeyEvent.VK_G,  Toolkit.getDefaultToolkit().getMenuShortcutKeyMask());
    837
    838     getTabbedPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
    839       ksRight, "MoveRight");
    840     getTabbedPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
    841       ksLeft, "MoveLeft");
    842     getTabbedPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
    843       ksGotoLine, "GotoLine");
    844
    845     Action moveRight =
    846       new AbstractAction() {
    847         public void actionPerformed(ActionEvent e) {
    848           int temp = getTabbedPane().getSelectedIndex();
    849           ++temp;
    850
    851           if (temp != getTabbedPane().getTabCount()) {
    852             getTabbedPane().setSelectedTab(temp);
    853           }
    854         }
    855       };
    856
    857     Action moveLeft =
    858       new AbstractAction() {
    859         public void actionPerformed(ActionEvent e) {
    860           int temp = getTabbedPane().getSelectedIndex();
    861           --temp;
    862
    863           if (temp > -1) {
    864             getTabbedPane().setSelectedTab(temp);
    865           }
    866         }
    867       };
    868
    869     Action gotoLine =
    870       new AbstractAction() {
    871         public void actionPerformed(ActionEvent e) {
    872           String inputLine = JOptionPane.showInputDialog(LogUI.this, "Enter the line number to go:", "Goto Line", -1);
    873           try {
    874         	  int lineNumber = Integer.parseInt(inputLine);
    875               int row = getCurrentLogPanel().setSelectedEvent(lineNumber);
    876               if (row == -1) {
    877                   JOptionPane.showMessageDialog(LogUI.this, "You have entered an invalid line number", "Error", 0);
    878               }
    879           } catch (NumberFormatException nfe) {
    880               JOptionPane.showMessageDialog(LogUI.this, "You have entered an invalid line number", "Error", 0);
    881           }
    882         }
    883       };
    884
    885
    886     getTabbedPane().getActionMap().put("MoveRight", moveRight);
    887     getTabbedPane().getActionMap().put("MoveLeft", moveLeft);
    888     getTabbedPane().getActionMap().put("GotoLine", gotoLine);
    889
    890     /**
    891          * We listen for double clicks, and auto-undock currently selected Tab if
    892          * the mouse event location matches the currently selected tab
    893          */
    894     getTabbedPane().addMouseListener(
    895       new MouseAdapter() {
    896         public void mouseClicked(MouseEvent e) {
    897           super.mouseClicked(e);
    898
    899           if (
    900             (e.getClickCount() > 1)
    901               && ((e.getModifiers() & InputEvent.BUTTON1_MASK) > 0)) {
    902             int tabIndex = getTabbedPane().getSelectedIndex();
    903
    904             if (
    905               (tabIndex != -1)
    906                 && (tabIndex == getTabbedPane().getSelectedIndex())) {
    907               LogPanel logPanel = getCurrentLogPanel();
    908
    909               if (logPanel != null) {
    910                 logPanel.undock();
    911               }
    912             }
    913           }
    914         }
    915       });
    916
    917     panePanel.add(getTabbedPane());
    918     addWelcomePanel();
    919
    920     getContentPane().add(toolbar, BorderLayout.NORTH);
    921     getContentPane().add(statusBar, BorderLayout.SOUTH);
    922
    923     mainReceiverSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, panePanel, receiversPanel);
    924     dividerSize = mainReceiverSplitPane.getDividerSize();
    925     mainReceiverSplitPane.setDividerLocation(-1);
    926
    927     getContentPane().add(mainReceiverSplitPane, BorderLayout.CENTER);
    928
    929     /**
    930      * We need to make sure that all the internal GUI components have been added to the
    931      * JFrame so that any plugns that get activated during initPlugins(...) method
    932      * have access to inject menus
    933      */
    934     initPlugins(pluginRegistry);
    935
    936     mainReceiverSplitPane.setResizeWeight(1.0);
    937     addWindowListener(
    938       new WindowAdapter() {
    939         public void windowClosing(WindowEvent event) {
    940           exit();
    941         }
    942       });
    943     preferencesFrame.setTitle("'Application-wide Preferences");
    944     preferencesFrame.setIconImage(
    945       ((ImageIcon) ChainsawIcons.ICON_PREFERENCES).getImage());
    946     preferencesFrame.getContentPane().add(applicationPreferenceModelPanel);
    947
    948     preferencesFrame.setSize(750, 520);
    949
    950     Dimension screenDimension = Toolkit.getDefaultToolkit().getScreenSize();
    951     preferencesFrame.setLocation(
    952       new Point(
    953         (screenDimension.width / 2) - (preferencesFrame.getSize().width / 2),
    954         (screenDimension.height / 2) - (preferencesFrame.getSize().height / 2)));
    955
    956     pack();
    957
    958     final JPopupMenu tabPopup = new JPopupMenu();
    959     final Action hideCurrentTabAction =
    960       new AbstractAction("Hide") {
    961         public void actionPerformed(ActionEvent e) {
    962           Component selectedComp = getTabbedPane().getSelectedComponent();
    963           if (selectedComp instanceof LogPanel) {
    964             displayPanel(getCurrentLogPanel().getIdentifier(), false);
    965             tbms.stateChange();
    966           } else {
    967             getTabbedPane().remove(selectedComp);
    968           }
    969         }
    970       };
    971
    972     final Action hideOtherTabsAction =
    973       new AbstractAction("Hide Others") {
    974         public void actionPerformed(ActionEvent e) {
    975           Component selectedComp = getTabbedPane().getSelectedComponent();
    976           String currentName;
    977           if (selectedComp instanceof LogPanel) {
    978             currentName = getCurrentLogPanel().getIdentifier();
    979           } else if (selectedComp instanceof WelcomePanel) {
    980             currentName = ChainsawTabbedPane.WELCOME_TAB;
    981           } else {
    982             currentName = ChainsawTabbedPane.ZEROCONF;
    983           }
    984
    985           int count = getTabbedPane().getTabCount();
    986           int index = 0;
    987
    988           for (int i = 0; i < count; i++) {
    989             String name = getTabbedPane().getTitleAt(index);
    990
    991             if (
    992               getPanelMap().keySet().contains(name)
    993                 && !name.equals(currentName)) {
    994               displayPanel(name, false);
    995               tbms.stateChange();
    996             } else {
    997               index++;
    998             }
    999           }
    1000         }
    1001       };
    1002
    1003     Action showHiddenTabsAction =
    1004       new AbstractAction("Show All Hidden") {
    1005         public void actionPerformed(ActionEvent e) {
    1006           for (Iterator iter = getPanels().entrySet().iterator();
    1007               iter.hasNext();) {
    1008           	Map.Entry entry = (Map.Entry)iter.next();
    1009           	Boolean docked = (Boolean)entry.getValue();
    1010           	if (docked.booleanValue()) {
    1011 	            String identifier = (String) entry.getKey();
    1012 	            int count = getTabbedPane().getTabCount();
    1013 	            boolean found = false;
    1014
    1015 	            for (int i = 0; i < count; i++) {
    1016 	              String name = getTabbedPane().getTitleAt(i);
    1017
    1018 	              if (name.equals(identifier)) {
    1019 	                found = true;
    1020
    1021 	                break;
    1022 	              }
    1023 	            }
    1024
    1025 	            if (!found) {
    1026 	              displayPanel(identifier, true);
    1027 	              tbms.stateChange();
    1028 	            }
    1029           	}
    1030           }
    1031         }
    1032       };
    1033
    1034     tabPopup.add(hideCurrentTabAction);
    1035     tabPopup.add(hideOtherTabsAction);
    1036     tabPopup.addSeparator();
    1037     tabPopup.add(showHiddenTabsAction);
    1038
    1039     final PopupListener tabPopupListener = new PopupListener(tabPopup);
    1040     getTabbedPane().addMouseListener(tabPopupListener);
    1041
    1042     this.handler.addPropertyChangeListener(
    1043       "dataRate",
    1044       new PropertyChangeListener() {
    1045         public void propertyChange(PropertyChangeEvent evt) {
    1046           double dataRate = ((Double) evt.getNewValue()).doubleValue();
    1047           statusBar.setDataRate(dataRate);
    1048         }
    1049       });
    1050
    1051     getSettingsManager().addSettingsListener(this);
    1052     getSettingsManager().addSettingsListener(MRUFileListPreferenceSaver.getInstance());
    1053     getSettingsManager().addSettingsListener(receiversPanel);
    1054     try {
    1055       //if an uncaught exception is thrown, allow the UI to continue to load
    1056       getSettingsManager().loadSettings();
    1057     } catch (Exception e) {
    1058       e.printStackTrace();
    1059     }
    1060     //app preferences have already been loaded (and configuration url possibly set to blank if being overridden)
    1061     //but we need a listener so the settings will be saved on exit (added after loadsettings was called)
    1062     getSettingsManager().addSettingsListener(new ApplicationPreferenceModelSaver(applicationPreferenceModel));
    1063
    1064     setVisible(true);
    1065
    1066     if (applicationPreferenceModel.isReceivers()) {
    1067       showReceiverPanel();
    1068     } else {
    1069       hideReceiverPanel();
    1070     }
    1071
    1072     removeSplash();
    1073
    1074     synchronized (initializationLock) {
    1075       isGUIFullyInitialized = true;
    1076       initializationLock.notifyAll();
    1077     }
    1078
    1079     if (
    1080       noReceiversDefined
    1081         && applicationPreferenceModel.isShowNoReceiverWarning()) {
    1082       SwingHelper.invokeOnEDT(new Runnable() {
    1083           public void run() {
    1084               showReceiverConfigurationPanel();
    1085           }
    1086       });
    1087     }
    1088
    1089     Container container = tutorialFrame.getContentPane();
    1090     final JEditorPane tutorialArea = new JEditorPane();
    1091     tutorialArea.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 5));
    1092     tutorialArea.setEditable(false);
    1093     container.setLayout(new BorderLayout());
    1094
    1095     try {
    1096       tutorialArea.setPage(ChainsawConstants.TUTORIAL_URL);
    1097       JTextComponentFormatter.applySystemFontAndSize(tutorialArea);
    1098
    1099       container.add(new JScrollPane(tutorialArea), BorderLayout.CENTER);
    1100     } catch (Exception e) {
    1101       MessageCenter.getInstance().getLogger().error(
    1102         "Error occurred loading the Tutorial", e);
    1103     }
    1104
    1105     tutorialFrame.setIconImage(new ImageIcon(ChainsawIcons.HELP).getImage());
    1106     tutorialFrame.setSize(new Dimension(640, 480));
    1107
    1108     final Action startTutorial =
    1109       new AbstractAction(
    1110         "Start Tutorial", new ImageIcon(ChainsawIcons.ICON_RESUME_RECEIVER)) {
    1111         public void actionPerformed(ActionEvent e) {
    1112           if (
    1113             JOptionPane.showConfirmDialog(
    1114                 null,
    1115                 "This will start 3 \"Generator\" receivers for use in the Tutorial.  Is that ok?",
    1116                 "Confirm", JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) {
    1117             new Thread(new Tutorial()).start();
    1118             putValue("TutorialStarted", Boolean.TRUE);
    1119           } else {
    1120             putValue("TutorialStarted", Boolean.FALSE);
    1121           }
    1122         }
    1123       };
    1124
    1125     final Action stopTutorial =
    1126       new AbstractAction(
    1127         "Stop Tutorial", new ImageIcon(ChainsawIcons.ICON_STOP_RECEIVER)) {
    1128         public void actionPerformed(ActionEvent e) {
    1129           if (
    1130             JOptionPane.showConfirmDialog(
    1131                 null,
    1132                 "This will stop all of the \"Generator\" receivers used in the Tutorial, but leave any other Receiver untouched.  Is that ok?",
    1133                 "Confirm", JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) {
    1134             new Thread(
    1135               new Runnable() {
    1136                 public void run() {
    1137                   LoggerRepository repo = LogManager.getLoggerRepository();
    1138                   if (repo instanceof LoggerRepositoryEx) {
    1139                       PluginRegistry pluginRegistry = ((LoggerRepositoryEx) repo).getPluginRegistry();
    1140                       List list = pluginRegistry.getPlugins(Generator.class);
    1141
    1142                       for (Iterator iter = list.iterator(); iter.hasNext();) {
    1143                          Plugin plugin = (Plugin) iter.next();
    1144                          pluginRegistry.stopPlugin(plugin.getName());
    1145                       }
    1146                    }
    1147                 }
    1148               }).start();
    1149             setEnabled(false);
    1150             startTutorial.putValue("TutorialStarted", Boolean.FALSE);
    1151           }
    1152         }
    1153       };
    1154
    1155     stopTutorial.putValue(
    1156       Action.SHORT_DESCRIPTION,
    1157       "Removes all of the Tutorials Generator Receivers, leaving all other Receivers untouched");
    1158     startTutorial.putValue(
    1159       Action.SHORT_DESCRIPTION,
    1160       "Begins the Tutorial, starting up some Generator Receivers so you can see Chainsaw in action");
    1161     stopTutorial.setEnabled(false);
    1162
    1163     final SmallToggleButton startButton = new SmallToggleButton(startTutorial);
    1164     PropertyChangeListener pcl =
    1165       new PropertyChangeListener() {
    1166         public void propertyChange(PropertyChangeEvent evt) {
    1167           stopTutorial.setEnabled(
    1168             ((Boolean) startTutorial.getValue("TutorialStarted")).equals(
    1169               Boolean.TRUE));
    1170           startButton.setSelected(stopTutorial.isEnabled());
    1171         }
    1172       };
    1173
    1174     startTutorial.addPropertyChangeListener(pcl);
    1175     stopTutorial.addPropertyChangeListener(pcl);
    1176
    1177     pluginRegistry.addPluginListener(
    1178       new PluginListener() {
    1179         public void pluginStarted(PluginEvent e) {
    1180         }
    1181
    1182         public void pluginStopped(PluginEvent e) {
    1183           List list = pluginRegistry.getPlugins(Generator.class);
    1184
    1185           if (list.size() == 0) {
    1186             startTutorial.putValue("TutorialStarted", Boolean.FALSE);
    1187           }
    1188         }
    1189       });
    1190
    1191     final SmallButton stopButton = new SmallButton(stopTutorial);
    1192
    1193     final JToolBar tutorialToolbar = new JToolBar();
    1194     tutorialToolbar.setFloatable(false);
    1195     tutorialToolbar.add(startButton);
    1196     tutorialToolbar.add(stopButton);
    1197     container.add(tutorialToolbar, BorderLayout.NORTH);
    1198     tutorialArea.addHyperlinkListener(
    1199       new HyperlinkListener() {
    1200         public void hyperlinkUpdate(HyperlinkEvent e) {
    1201           if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
    1202             if (e.getDescription().equals("StartTutorial")) {
    1203               startTutorial.actionPerformed(null);
    1204             } else if (e.getDescription().equals("StopTutorial")) {
    1205               stopTutorial.actionPerformed(null);
    1206             } else {
    1207               try {
    1208                 tutorialArea.setPage(e.getURL());
    1209               } catch (IOException e1) {
    1210                 MessageCenter.getInstance().getLogger().error(
    1211                   "Failed to change the URL for the Tutorial", e1);
    1212               }
    1213             }
    1214           }
    1215         }
    1216       });
    1217
    1218     /**
    1219      * loads the saved tab settings and if there are hidden tabs,
    1220      * hide those tabs out of currently loaded tabs..
    1221      */
    1222
    1223     if (!getTabbedPane().tabSetting.isWelcome()){
    1224       displayPanel(ChainsawTabbedPane.WELCOME_TAB, false);
    1225     }
    1226     if (!getTabbedPane().tabSetting.isZeroconf()){
    1227       displayPanel(ChainsawTabbedPane.ZEROCONF, false);
    1228     }
    1229     tbms.stateChange();
    1230
    1231   }
    1232
    1233 /**
    1234    * Display the log tree pane, using the last known divider location
    1235    */
    1236   private void showReceiverPanel() {
    1237     mainReceiverSplitPane.setDividerSize(dividerSize);
    1238     mainReceiverSplitPane.setDividerLocation(lastMainReceiverSplitLocation);
    1239     receiversPanel.setVisible(true);
    1240     mainReceiverSplitPane.repaint();
    1241   }
    1242
    1243   /**
    1244    * Hide the log tree pane, holding the current divider location for later use
    1245    */
    1246   private void hideReceiverPanel() {
    1247     //subtract one to make sizes match
    1248     int currentSize = mainReceiverSplitPane.getWidth() - mainReceiverSplitPane.getDividerSize();
    1249     if (mainReceiverSplitPane.getDividerLocation() > -1) {
    1250         if (!(((mainReceiverSplitPane.getDividerLocation() + 1) == currentSize)
    1251                 || ((mainReceiverSplitPane.getDividerLocation() - 1) == 0))) {
    1252                     lastMainReceiverSplitLocation = ((double) mainReceiverSplitPane
    1253                         .getDividerLocation() / currentSize);
    1254         }
    1255     }
    1256     mainReceiverSplitPane.setDividerSize(0);
    1257     receiversPanel.setVisible(false);
    1258     mainReceiverSplitPane.repaint();
    1259   }
    1260
    1261   private void initSocketConnectionListener() {
    1262     final SocketNodeEventListener socketListener =
    1263       new SocketNodeEventListener() {
    1264         public void socketOpened(String remoteInfo) {
    1265           statusBar.remoteConnectionReceived(remoteInfo);
    1266         }
    1267
    1268         public void socketClosedEvent(Exception e) {
    1269           MessageCenter.getInstance().getLogger().info(
    1270             "Connection lost! :: " + e.getMessage());
    1271         }
    1272       };
    1273
    1274     PluginListener pluginListener =
    1275       new PluginListener() {
    1276         public void pluginStarted(PluginEvent e) {
    1277           MessageCenter.getInstance().getLogger().info(
    1278             e.getPlugin().getName() + " started!");
    1279
    1280           Method method = getAddListenerMethod(e.getPlugin());
    1281
    1282           if (method != null) {
    1283             try {
    1284               method.invoke(e.getPlugin(), new Object[] { socketListener });
    1285             } catch (Exception ex) {
    1286               MessageCenter.getInstance().getLogger().error(
    1287                 "Failed to add a SocketNodeEventListener", ex);
    1288             }
    1289           }
    1290         }
    1291
    1292         Method getRemoveListenerMethod(Plugin p) {
    1293           try {
    1294             return p.getClass().getMethod(
    1295               "removeSocketNodeEventListener",
    1296               new Class[] { SocketNodeEventListener.class });
    1297           } catch (Exception e) {
    1298             return null;
    1299           }
    1300         }
    1301
    1302         Method getAddListenerMethod(Plugin p) {
    1303           try {
    1304             return p.getClass().getMethod(
    1305               "addSocketNodeEventListener",
    1306               new Class[] { SocketNodeEventListener.class });
    1307           } catch (Exception e) {
    1308             return null;
    1309           }
    1310         }
    1311
    1312         public void pluginStopped(PluginEvent e) {
    1313           Method method = getRemoveListenerMethod(e.getPlugin());
    1314
    1315           if (method != null) {
    1316             try {
    1317               method.invoke(e.getPlugin(), new Object[] { socketListener });
    1318             } catch (Exception ex) {
    1319               MessageCenter.getInstance().getLogger().error(
    1320                 "Failed to remove SocketNodeEventListener", ex);
    1321             }
    1322           }
    1323
    1324           MessageCenter.getInstance().getLogger().info(
    1325             e.getPlugin().getName() + " stopped!");
    1326         }
    1327       };
    1328
    1329     pluginRegistry.addPluginListener(pluginListener);
    1330   }
    1331
    1332   private void initPrefModelListeners() {
    1333     applicationPreferenceModel.addPropertyChangeListener(
    1334       "identifierExpression",
    1335       new PropertyChangeListener() {
    1336         public void propertyChange(PropertyChangeEvent evt) {
    1337           handler.setIdentifierExpression(evt.getNewValue().toString());
    1338         }
    1339       });
    1340     handler.setIdentifierExpression(applicationPreferenceModel.getIdentifierExpression());
    1341
    1342
    1343     applicationPreferenceModel.addPropertyChangeListener(
    1344       "toolTipDisplayMillis",
    1345       new PropertyChangeListener() {
    1346         public void propertyChange(PropertyChangeEvent evt) {
    1347           ToolTipManager.sharedInstance().setDismissDelay(
    1348             ((Integer) evt.getNewValue()).intValue());
    1349         }
    1350       });
    1351     ToolTipManager.sharedInstance().setDismissDelay(
    1352       applicationPreferenceModel.getToolTipDisplayMillis());
    1353
    1354     applicationPreferenceModel.addPropertyChangeListener(
    1355       "responsiveness",
    1356       new PropertyChangeListener() {
    1357         public void propertyChange(PropertyChangeEvent evt) {
    1358           int value = ((Integer) evt.getNewValue()).intValue();
    1359           handler.setQueueInterval((value * 1000) - 750);
    1360         }
    1361       });
    1362     handler.setQueueInterval((applicationPreferenceModel.getResponsiveness() * 1000) - 750);
    1363
    1364     applicationPreferenceModel.addPropertyChangeListener(
    1365       "tabPlacement",
    1366       new PropertyChangeListener() {
    1367         public void propertyChange(final PropertyChangeEvent evt) {
    1368           SwingUtilities.invokeLater(
    1369             new Runnable() {
    1370               public void run() {
    1371                 int placement = ((Integer) evt.getNewValue()).intValue();
    1372
    1373                 switch (placement) {
    1374                 case SwingConstants.TOP:
    1375                 case SwingConstants.BOTTOM:
    1376                   tabbedPane.setTabPlacement(placement);
    1377
    1378                   break;
    1379
    1380                 default:
    1381                   break;
    1382                 }
    1383               }
    1384             });
    1385         }
    1386       });
    1387
    1388     applicationPreferenceModel.addPropertyChangeListener(
    1389       "statusBar",
    1390       new PropertyChangeListener() {
    1391         public void propertyChange(PropertyChangeEvent evt) {
    1392           boolean value = ((Boolean) evt.getNewValue()).booleanValue();
    1393           setStatusBarVisible(value);
    1394         }
    1395       });
    1396     setStatusBarVisible(applicationPreferenceModel.isStatusBar());
    1397
    1398     applicationPreferenceModel.addPropertyChangeListener(
    1399       "receivers",
    1400       new PropertyChangeListener() {
    1401         public void propertyChange(PropertyChangeEvent evt) {
    1402           boolean value = ((Boolean) evt.getNewValue()).booleanValue();
    1403
    1404           if (value) {
    1405             showReceiverPanel();
    1406           } else {
    1407             hideReceiverPanel();
    1408           }
    1409         }
    1410       });
    1411 //    if (applicationPreferenceModel.isReceivers()) {
    1412 //      showReceiverPanel();
    1413 //    } else {
    1414 //      hideReceiverPanel();
    1415 //    }
    1416
    1417
    1418     applicationPreferenceModel.addPropertyChangeListener(
    1419       "toolbar",
    1420       new PropertyChangeListener() {
    1421         public void propertyChange(PropertyChangeEvent evt) {
    1422           boolean value = ((Boolean) evt.getNewValue()).booleanValue();
    1423           toolbar.setVisible(value);
    1424         }
    1425       });
    1426     toolbar.setVisible(applicationPreferenceModel.isToolbar());
    1427
    1428   }
    1429
    1430   /**
    1431    * Displays a dialog which will provide options for selecting a configuration
    1432    */
    1433   private void showReceiverConfigurationPanel() {
    1434     SwingUtilities.invokeLater(
    1435       new Runnable() {
    1436         public void run() {
    1437           final JDialog dialog = new JDialog(LogUI.this, true);
    1438           dialog.setTitle("Load events into Chainsaw");
    1439           dialog.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
    1440
    1441           dialog.setResizable(false);
    1442
    1443           receiverConfigurationPanel.setCompletionActionListener(
    1444             new ActionListener() {
    1445               public void actionPerformed(ActionEvent e) {
    1446                 dialog.setVisible(false);
    1447
    1448             if (receiverConfigurationPanel.getModel().isCancelled()) {
    1449               return;
    1450             }
    1451             applicationPreferenceModel.setShowNoReceiverWarning(!receiverConfigurationPanel.isDontWarnMeAgain());
    1452             //remove existing plugins
    1453             List plugins = pluginRegistry.getPlugins();
    1454             for (Iterator iter = plugins.iterator();iter.hasNext();) {
    1455                 Plugin plugin = (Plugin)iter.next();
    1456                 //don't stop ZeroConfPlugin if it is registered
    1457                 if (!plugin.getName().toLowerCase(Locale.ENGLISH).contains("zeroconf")) {
    1458                   pluginRegistry.stopPlugin(plugin.getName());
    1459                 }
    1460             }
    1461             URL configURL = null;
    1462
    1463             if (receiverConfigurationPanel.getModel().isNetworkReceiverMode()) {
    1464               int port = receiverConfigurationPanel.getModel().getNetworkReceiverPort();
    1465
    1466               try {
    1467                 Class receiverClass = receiverConfigurationPanel.getModel().getNetworkReceiverClass();
    1468                 Receiver networkReceiver = (Receiver) receiverClass.newInstance();
    1469                 networkReceiver.setName(receiverClass.getSimpleName() + "-" + port);
    1470
    1471                 Method portMethod =
    1472                   networkReceiver.getClass().getMethod(
    1473                     "setPort", new Class[] { int.class });
    1474                 portMethod.invoke(
    1475                   networkReceiver, new Object[] { new Integer(port) });
    1476
    1477                 networkReceiver.setThreshold(Level.TRACE);
    1478
    1479                 pluginRegistry.addPlugin(networkReceiver);
    1480                 networkReceiver.activateOptions();
    1481                 receiversPanel.updateReceiverTreeInDispatchThread();
    1482               } catch (Exception e3) {
    1483                 MessageCenter.getInstance().getLogger().error(
    1484                   "Error creating Receiver", e3);
    1485                 MessageCenter.getInstance().getLogger().info(
    1486                   "An error occurred creating your Receiver");
    1487               }
    1488             } else if (receiverConfigurationPanel.getModel().isLog4jConfig()) {
    1489               File log4jConfigFile = receiverConfigurationPanel.getModel().getLog4jConfigFile();
    1490               if (log4jConfigFile != null) {
    1491                 try {
    1492                   Map entries = LogFilePatternLayoutBuilder.getAppenderConfiguration(log4jConfigFile);
    1493                   for (Iterator iter = entries.entrySet().iterator();iter.hasNext();) {
    1494                     try {
    1495                       Map.Entry entry = (Map.Entry)iter.next();
    1496                       String name = (String) entry.getKey();
    1497                       Map values = (Map) entry.getValue();
    1498                       //values: conversion, file
    1499                       String conversionPattern = values.get("conversion").toString();
    1500                       File file = new File(values.get("file").toString());
    1501                       URL fileURL = file.toURI().toURL();
    1502                       String timestampFormat = LogFilePatternLayoutBuilder.getTimeStampFormat(conversionPattern);
    1503                       String receiverPattern = LogFilePatternLayoutBuilder.getLogFormatFromPatternLayout(conversionPattern);
    1504                       VFSLogFilePatternReceiver fileReceiver = new VFSLogFilePatternReceiver();
    1505                       fileReceiver.setName(name);
    1506                       fileReceiver.setAutoReconnect(true);
    1507                       fileReceiver.setContainer(LogUI.this);
    1508                       fileReceiver.setAppendNonMatches(true);
    1509                       fileReceiver.setFileURL(fileURL.toURI().toString());
    1510                       fileReceiver.setTailing(true);
    1511                       fileReceiver.setLogFormat(receiverPattern);
    1512                       fileReceiver.setTimestampFormat(timestampFormat);
    1513                       fileReceiver.setThreshold(Level.TRACE);
    1514                       pluginRegistry.addPlugin(fileReceiver);
    1515                       fileReceiver.activateOptions();
    1516                       receiversPanel.updateReceiverTreeInDispatchThread();
    1517                     } catch (URISyntaxException e1) {
    1518                       e1.printStackTrace();
    1519                     }
    1520                   }
    1521                 } catch (IOException e1) {
    1522                   e1.printStackTrace();
    1523                 }
    1524               }
    1525             } else if (receiverConfigurationPanel.getModel().isLoadConfig()) {
    1526                   configURL = receiverConfigurationPanel.getModel().getConfigToLoad();
    1527             } else if (receiverConfigurationPanel.getModel().isLogFileReceiverConfig()) {
    1528               try {
    1529                   URL fileURL = receiverConfigurationPanel.getModel().getLogFileURL();
    1530                   if (fileURL != null) {
    1531                       VFSLogFilePatternReceiver fileReceiver = new VFSLogFilePatternReceiver();
    1532                       fileReceiver.setName(fileURL.getFile());
    1533                       fileReceiver.setAutoReconnect(true);
    1534                       fileReceiver.setContainer(LogUI.this);
    1535                       fileReceiver.setAppendNonMatches(true);
    1536                       fileReceiver.setFileURL(fileURL.toURI().toString());
    1537                       fileReceiver.setTailing(true);
    1538                       if (receiverConfigurationPanel.getModel().isPatternLayoutLogFormat()) {
    1539                           fileReceiver.setLogFormat(LogFilePatternLayoutBuilder.getLogFormatFromPatternLayout(receiverConfigurationPanel.getModel().getLogFormat()));
    1540                       } else {
    1541                           fileReceiver.setLogFormat(receiverConfigurationPanel.getModel().getLogFormat());
    1542                       }
    1543                       fileReceiver.setTimestampFormat(receiverConfigurationPanel.getModel().getLogFormatTimestampFormat());
    1544                       fileReceiver.setThreshold(Level.TRACE);
    1545
    1546                       pluginRegistry.addPlugin(fileReceiver);
    1547                       fileReceiver.activateOptions();
    1548                       receiversPanel.updateReceiverTreeInDispatchThread();
    1549                   }
    1550               } catch (Exception e2) {
    1551                   MessageCenter.getInstance().getLogger().error(
    1552                     "Error creating Receiver", e2);
    1553                   MessageCenter.getInstance().getLogger().info(
    1554                     "An error occurred creating your Receiver");
    1555               }
    1556             }
    1557               if (configURL == null && receiverConfigurationPanel.isDontWarnMeAgain()) {
    1558                 //use the saved config file as the config URL if defined
    1559                 if (receiverConfigurationPanel.getModel().getSaveConfigFile() != null) {
    1560                   try {
    1561                     configURL = receiverConfigurationPanel.getModel().getSaveConfigFile().toURI().toURL();
    1562                   } catch (MalformedURLException e1) {
    1563                     e1.printStackTrace();
    1564                   }
    1565                 } else {
    1566                   //no saved config defined but don't warn me is checked - use default config
    1567                   configURL = receiverConfigurationPanel.getModel().getDefaultConfigFileURL();
    1568                 }
    1569               }
    1570               if (configURL != null) {
    1571                 MessageCenter.getInstance().getLogger().debug(
    1572                   "Initialiazing Log4j with " + configURL.toExternalForm());
    1573                 final URL finalURL = configURL;
    1574                 new Thread(
    1575                   new Runnable() {
    1576                     public void run() {
    1577                       if (receiverConfigurationPanel.isDontWarnMeAgain()) {
    1578                           applicationPreferenceModel.setConfigurationURL(finalURL.toExternalForm());
    1579                       } else {
    1580                           try {
    1581                               if (new File(finalURL.toURI()).exists()) {
    1582                                   loadConfigurationUsingPluginClassLoader(finalURL);
    1583                               }
    1584                           }
    1585                           catch (URISyntaxException e) {
    1586                               //ignore
    1587                           }
    1588                       }
    1589
    1590                       receiversPanel.updateReceiverTreeInDispatchThread();
    1591                     }
    1592                   }).start();
    1593               }
    1594                 File saveConfigFile = receiverConfigurationPanel.getModel().getSaveConfigFile();
    1595                 if (saveConfigFile != null) {
    1596                   ReceiversHelper.getInstance().saveReceiverConfiguration(saveConfigFile);
    1597                 }
    1598           }
    1599         });
    1600
    1601           receiverConfigurationPanel.setDialog(dialog);
    1602           dialog.getContentPane().add(receiverConfigurationPanel);
    1603
    1604           dialog.pack();
    1605
    1606           Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
    1607           dialog.setLocation(
    1608             (screenSize.width / 2) - (dialog.getWidth() / 2),
    1609             (screenSize.height / 2) - (dialog.getHeight() / 2));
    1610
    1611           dialog.setVisible(true);
    1612         }
    1613       });
    1614   }
    1615
    1616   /**
    1617    * Exits the application, ensuring Settings are saved.
    1618    *
    1619    */
    1620   public boolean exit() {
    1621     getSettingsManager().saveSettings();
    1622
    1623     return shutdown();
    1624   }
    1625
    1626   void addWelcomePanel() {
    1627     getTabbedPane().insertTab(
    1628       ChainsawTabbedPane.WELCOME_TAB,  new ImageIcon(ChainsawIcons.ABOUT),welcomePanel,
    1629       "Welcome/Help", 0);
    1630     getTabbedPane().setSelectedComponent(welcomePanel);
    1631     getPanelMap().put(ChainsawTabbedPane.WELCOME_TAB, welcomePanel);
    1632   }
    1633
    1634   void removeWelcomePanel() {
    1635     EventQueue.invokeLater(new Runnable()
    1636     {
    1637         public void run()
    1638         {
    1639             if (getTabbedPane().containsWelcomePanel()) {
    1640               getTabbedPane().remove(
    1641                 getTabbedPane().getComponentAt(getTabbedPane().indexOfTab(ChainsawTabbedPane.WELCOME_TAB)));
    1642             }
    1643         }
    1644     });
    1645   }
    1646
    1647   ChainsawStatusBar getStatusBar() {
    1648     return statusBar;
    1649   }
    1650
    1651   public void showApplicationPreferences() {
    1652     applicationPreferenceModelPanel.updateModel();
    1653     preferencesFrame.setVisible(true);
    1654   }
    1655
    1656   public void showReceiverConfiguration() {
    1657       showReceiverConfigurationPanel();
    1658   }
    1659
    1660   public void showAboutBox() {
    1661     if (aboutBox == null) {
    1662       aboutBox = new ChainsawAbout(this);
    1663     }
    1664
    1665     aboutBox.setVisible(true);
    1666   }
    1667
    1668   Map getPanels() {
    1669     Map m = new HashMap();
    1670     Set panelSet = getPanelMap().entrySet();
    1671     Iterator iter = panelSet.iterator();
    1672
    1673     while (iter.hasNext()) {
    1674       Map.Entry entry = (Map.Entry) iter.next();
    1675       Object o = entry.getValue();
    1676       boolean valueToSend;
    1677       if (o instanceof LogPanel){
    1678         valueToSend = ((DockablePanel) entry.getValue()).isDocked();
    1679       } else {
    1680         valueToSend = true;
    1681       }
    1682       m.put(entry.getKey(), new Boolean(valueToSend));
    1683     }
    1684
    1685     return m;
    1686   }
    1687
    1688   void displayPanel(String panelName, boolean display) {
    1689     Component p = (Component)getPanelMap().get(panelName);
    1690
    1691     int index = getTabbedPane().indexOfTab(panelName);
    1692
    1693     if ((index == -1) && display) {
    1694       getTabbedPane().addTab(panelName, p);
    1695     }
    1696
    1697     if ((index > -1) && !display) {
    1698       getTabbedPane().removeTabAt(index);
    1699     }
    1700   }
    1701
    1702
    1703   /**
    1704    * Shutsdown by ensuring the Appender gets a chance to close.
    1705    */
    1706   public boolean shutdown() {
    1707     if (getApplicationPreferenceModel().isConfirmExit()) {
    1708       if (
    1709         JOptionPane.showConfirmDialog(
    1710             LogUI.this, "Are you sure you want to exit Chainsaw?",
    1711             "Confirm Exit", JOptionPane.YES_NO_OPTION,
    1712             JOptionPane.INFORMATION_MESSAGE) != JOptionPane.YES_OPTION) {
    1713         return false;
    1714       }
    1715
    1716     }
    1717
    1718     final JWindow progressWindow = new JWindow();
    1719     final ProgressPanel panel = new ProgressPanel(1, 3, "Shutting down");
    1720     progressWindow.getContentPane().add(panel);
    1721     progressWindow.pack();
    1722
    1723     Point p = new Point(getLocation());
    1724     p.move((int) getSize().getWidth() >> 1, (int) getSize().getHeight() >> 1);
    1725     progressWindow.setLocation(p);
    1726     progressWindow.setVisible(true);
    1727
    1728     Runnable runnable =
    1729       new Runnable() {
    1730         public void run() {
    1731           try {
    1732             int progress = 1;
    1733             final int delay = 25;
    1734
    1735             handler.close();
    1736             panel.setProgress(progress++);
    1737
    1738             Thread.sleep(delay);
    1739
    1740             pluginRegistry.stopAllPlugins();
    1741             panel.setProgress(progress++);
    1742
    1743             Thread.sleep(delay);
    1744
    1745             panel.setProgress(progress++);
    1746             Thread.sleep(delay);
    1747           } catch (Exception e) {
    1748             e.printStackTrace();
    1749           }
    1750
    1751           fireShutdownEvent();
    1752           performShutdownAction();
    1753           progressWindow.setVisible(false);
    1754         }
    1755       };
    1756
    1757     if (OSXIntegration.IS_OSX) {
    1758         /**
    1759          * or OSX we do it in the current thread because otherwise returning
    1760          * will exit the process before it's had a chance to save things
    1761          *
    1762          */
    1763         runnable.run();
    1764     }else {
    1765         new Thread(runnable).start();
    1766     }
    1767     return true;
    1768   }
    1769
    1770   /**
    1771    * Ensures all the registered ShutdownListeners are notified.
    1772    */
    1773   private void fireShutdownEvent() {
    1774     ShutdownListener[] listeners =
    1775       (ShutdownListener[]) shutdownListenerList.getListeners(
    1776         ShutdownListener.class);
    1777
    1778     for (int i = 0; i < listeners.length; i++) {
    1779       listeners[i].shuttingDown();
    1780     }
    1781   }
    1782
    1783   /**
    1784    * Configures LogUI's with an action to execute when the user requests to
    1785    * exit the application, the default action is to exit the VM. This Action is
    1786    * called AFTER all the ShutdownListeners have been notified
    1787    *
    1788    * @param shutdownAction
    1789    */
    1790   public final void setShutdownAction(Action shutdownAction) {
    1791     this.shutdownAction = shutdownAction;
    1792   }
    1793
    1794   /**
    1795    * Using the current thread, calls the registed Shutdown action's
    1796    * actionPerformed(...) method.
    1797    *
    1798    */
    1799   private void performShutdownAction() {
    1800     MessageCenter.getInstance().getLogger().debug(
    1801       "Calling the shutdown Action. Goodbye!");
    1802
    1803     shutdownAction.actionPerformed(
    1804       new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "Shutting Down"));
    1805   }
    1806
    1807   /**
    1808    * Returns the currently selected LogPanel, if there is one, otherwise null
    1809    *
    1810    * @return current log panel
    1811    */
    1812   LogPanel getCurrentLogPanel() {
    1813     Component selectedTab = getTabbedPane().getSelectedComponent();
    1814
    1815     if (selectedTab instanceof LogPanel) {
    1816       return (LogPanel) selectedTab;
    1817     }
    1818
    1819     return null;
    1820   }
    1821
    1822   /**
    1823    * @param visible
    1824    */
    1825   private void setStatusBarVisible(final boolean visible) {
    1826     MessageCenter.getInstance().getLogger().debug(
    1827       "Setting StatusBar to " + visible);
    1828     SwingUtilities.invokeLater(
    1829       new Runnable() {
    1830         public void run() {
    1831           statusBar.setVisible(visible);
    1832         }
    1833       });
    1834   }
    1835
    1836   boolean isStatusBarVisible() {
    1837     return statusBar.isVisible();
    1838   }
    1839
    1840   /**
    1841    * DOCUMENT ME!
    1842    *
    1843    * @return DOCUMENT ME!
    1844    */
    1845   public String getActiveTabName() {
    1846     int index = getTabbedPane().getSelectedIndex();
    1847
    1848     if (index == -1) {
    1849       return null;
    1850     } else {
    1851       return getTabbedPane().getTitleAt(index);
    1852     }
    1853   }
    1854
    1855   /**
    1856    * Causes the Welcome Panel to become visible, and shows the URL specified as
    1857    * it's contents
    1858    *
    1859    * @param url
    1860    *                    for content to show
    1861    */
    1862   public void showHelp(URL url) {
    1863     ensureWelcomePanelVisible();
    1864     //    TODO ensure the Welcome Panel is the selected tab
    1865     getWelcomePanel().setURL(url);
    1866   }
    1867
    1868   /**
    1869    * DOCUMENT ME!
    1870    *
    1871    * @return welcome panel
    1872    */
    1873   private WelcomePanel getWelcomePanel() {
    1874     return welcomePanel;
    1875   }
    1876
    1877   /**
    1878    * DOCUMENT ME!
    1879    *
    1880    * @return log tree panel visible flag
    1881    */
    1882   public boolean isLogTreePanelVisible() {
    1883     if (getCurrentLogPanel() == null) {
    1884       return false;
    1885     }
    1886
    1887     return getCurrentLogPanel().isLogTreeVisible();
    1888   }
    1889
    1890   /**
    1891    * DOCUMENT ME!
    1892    *
    1893    * @return DOCUMENT ME!
    1894    */
    1895   public Map getPanelMap() {
    1896     return panelMap;
    1897   }
    1898
    1899   //  public Map getLevelMap() {
    1900   //    return levelMap;
    1901   //  }
    1902
    1903   /**
    1904    * DOCUMENT ME!
    1905    *
    1906    * @return DOCUMENT ME!
    1907    */
    1908   public SettingsManager getSettingsManager() {
    1909     return sm;
    1910   }
    1911
    1912   /**
    1913    * DOCUMENT ME!
    1914    *
    1915    * @return DOCUMENT ME!
    1916    */
    1917   public List getFilterableColumns() {
    1918     return filterableColumns;
    1919   }
    1920
    1921   /**
    1922    * DOCUMENT ME!
    1923    *
    1924    * @param tbms
    1925    *                    DOCUMENT ME!
    1926    */
    1927   public void setToolBarAndMenus(ChainsawToolBarAndMenus tbms) {
    1928     this.tbms = tbms;
    1929   }
    1930
    1931   /**
    1932    * DOCUMENT ME!
    1933    *
    1934    * @return DOCUMENT ME!
    1935    */
    1936   public ChainsawToolBarAndMenus getToolBarAndMenus() {
    1937     return tbms;
    1938   }
    1939
    1940   /**
    1941    * DOCUMENT ME!
    1942    *
    1943    * @return DOCUMENT ME!
    1944    */
    1945   public Map getTableMap() {
    1946     return tableMap;
    1947   }
    1948
    1949   /**
    1950    * DOCUMENT ME!
    1951    *
    1952    * @return DOCUMENT ME!
    1953    */
    1954   public Map getTableModelMap() {
    1955     return tableModelMap;
    1956   }
    1957
    1958   /**
    1959    * DOCUMENT ME!
    1960    *
    1961    * @param tabbedPane
    1962    *                    DOCUMENT ME!
    1963    */
    1964   public void setTabbedPane(ChainsawTabbedPane tabbedPane) {
    1965     this.tabbedPane = tabbedPane;
    1966   }
    1967
    1968   /**
    1969    * DOCUMENT ME!
    1970    *
    1971    * @return DOCUMENT ME!
    1972    */
    1973   public ChainsawTabbedPane getTabbedPane() {
    1974     return tabbedPane;
    1975   }
    1976
    1977   /**
    1978    * @return Returns the applicationPreferenceModel.
    1979    */
    1980   public final ApplicationPreferenceModel getApplicationPreferenceModel() {
    1981     return applicationPreferenceModel;
    1982   }
    1983
    1984   /**
    1985    * DOCUMENT ME!
    1986    */
    1987   public void setupTutorial() {
    1988     SwingUtilities.invokeLater(
    1989       new Runnable() {
    1990         public void run() {
    1991           Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
    1992           setLocation(0, getLocation().y);
    1993
    1994           double chainsawwidth = 0.7;
    1995           double tutorialwidth = 1 - chainsawwidth;
    1996           setSize((int) (screen.width * chainsawwidth), getSize().height);
    1997           invalidate();
    1998           validate();
    1999
    2000           Dimension size = getSize();
    2001           Point loc = getLocation();
    2002           tutorialFrame.setSize(
    2003             (int) (screen.width * tutorialwidth), size.height);
    2004           tutorialFrame.setLocation(loc.x + size.width, loc.y);
    2005           tutorialFrame.setVisible(true);
    2006         }
    2007       });
    2008   }
    2009
    2010   private void buildLogPanel(
    2011       boolean customExpression, final String ident, final List events)
    2012       throws IllegalArgumentException {
    2013       final LogPanel thisPanel = new LogPanel(getStatusBar(), ident, cyclicBufferSize, allColorizers, applicationPreferenceModel);
    2014
    2015       getSettingsManager().addSettingsListener(thisPanel);
    2016       getSettingsManager().configure(thisPanel);
    2017
    2018
    2019       /**
    2020                * Now add the panel as a batch listener so it can handle it's own
    2021                * batchs
    2022                */
    2023       if (customExpression) {
    2024         handler.addCustomEventBatchListener(ident, thisPanel);
    2025       } else {
    2026         identifierPanels.add(thisPanel);
    2027         handler.addEventBatchListener(thisPanel);
    2028       }
    2029
    2030       TabIconHandler iconHandler = new TabIconHandler(ident);
    2031       thisPanel.addEventCountListener(iconHandler);
    2032
    2033
    2034
    2035       tabbedPane.addChangeListener(iconHandler);
    2036
    2037       PropertyChangeListener toolbarMenuUpdateListener =
    2038         new PropertyChangeListener() {
    2039           public void propertyChange(PropertyChangeEvent evt) {
    2040             tbms.stateChange();
    2041           }
    2042         };
    2043
    2044       thisPanel.addPropertyChangeListener(toolbarMenuUpdateListener);
    2045       thisPanel.addPreferencePropertyChangeListener(toolbarMenuUpdateListener);
    2046
    2047       thisPanel.addPropertyChangeListener(
    2048         "docked",
    2049         new PropertyChangeListener() {
    2050           public void propertyChange(PropertyChangeEvent evt) {
    2051             LogPanel logPanel = (LogPanel) evt.getSource();
    2052
    2053             if (logPanel.isDocked()) {
    2054               getPanelMap().put(logPanel.getIdentifier(), logPanel);
    2055               getTabbedPane().addANewTab(
    2056                 logPanel.getIdentifier(), logPanel, null);
    2057               getTabbedPane().setSelectedTab(getTabbedPane().indexOfTab(logPanel.getIdentifier()));
    2058             } else {
    2059               getTabbedPane().remove(logPanel);
    2060             }
    2061           }
    2062         });
    2063
    2064       logger.debug("adding logpanel to tabbed pane: " + ident);
    2065
    2066       //NOTE: tab addition is a very fragile process - if you modify this code,
    2067       //verify the frames in the individual log panels initialize to their
    2068       //correct sizes
    2069       getTabbedPane().add(ident, thisPanel);
    2070       getPanelMap().put(ident, thisPanel);
    2071
    2072       /**
    2073                * Let the new LogPanel receive this batch
    2074                */
    2075
    2076       SwingUtilities.invokeLater(
    2077         new Runnable() {
    2078           public void run() {
    2079             getTabbedPane().addANewTab(
    2080               ident, thisPanel, new ImageIcon(ChainsawIcons.ANIM_RADIO_TOWER));
    2081               thisPanel.layoutComponents();
    2082             thisPanel.receiveEventBatch(ident, events);
    2083             if(!getTabbedPane().tabSetting.isChainsawLog()){
    2084               displayPanel("chainsaw-log", false);
    2085             }
    2086           }
    2087         });
    2088
    2089       String msg = "added tab " + ident;
    2090       MessageCenter.getInstance().getLogger().debug(msg);
    2091     }
    2092
    2093
    2094   public void createCustomExpressionLogPanel(String ident) {
    2095     //collect events matching the rule from all of the tabs
    2096     try {
    2097       List list = new ArrayList();
    2098       Rule rule = ExpressionRule.getRule(ident);
    2099       Iterator iter = identifierPanels.iterator();
    2100
    2101       while (iter.hasNext()) {
    2102         LogPanel panel = (LogPanel) iter.next();
    2103         Iterator iter2 = panel.getMatchingEvents(rule).iterator();
    2104
    2105         while (iter2.hasNext()) {
    2106           LoggingEventWrapper e = (LoggingEventWrapper) iter2.next();
    2107           list.add(e.getLoggingEvent());
    2108         }
    2109       }
    2110
    2111       buildLogPanel(true, ident, list);
    2112     } catch (IllegalArgumentException iae) {
    2113       MessageCenter.getInstance().getLogger().info(
    2114         "Unable to add tab using expression: " + ident + ", reason: "
    2115         + iae.getMessage());
    2116     }
    2117   }
    2118
    2119   /**
    2120    * Loads the log4j configuration file specified by the url, using
    2121    * the PluginClassLoader instance as a TCCL, but only replacing it temporarily, with the original
    2122    * TCCL being restored in a finally block to ensure consitency.
    2123    *
    2124    * @param url
    2125    */
    2126     private void loadConfigurationUsingPluginClassLoader(final URL url) {
    2127         ClassLoader classLoader = PluginClassLoaderFactory.getInstance().getClassLoader();
    2128         ClassLoader previousTCCL = Thread.currentThread().getContextClassLoader();
    2129
    2130         if(url!=null) {
    2131             try {
    2132               // we temporarily swap the TCCL so that plugins can find resources
    2133               Thread.currentThread().setContextClassLoader(classLoader);
    2134               try {
    2135                 DOMConfigurator.configure(url);
    2136               } catch (Exception e) {
    2137                 logger.warn("Unable to load configuration URL: " + url, e);
    2138               }
    2139             }finally{
    2140                 // now switch it back...
    2141                 Thread.currentThread().setContextClassLoader(previousTCCL);
    2142             }
    2143         }
    2144         ensureChainsawAppenderHandlerAdded();
    2145     }
    2146
    2147   private static void loadLookAndFeelUsingPluginClassLoader(String lookAndFeelClassName) {
    2148       ClassLoader classLoader = PluginClassLoaderFactory.getInstance().getClassLoader();
    2149       ClassLoader previousTCCL = Thread.currentThread().getContextClassLoader();
    2150           try {
    2151             // we temporarily swap the TCCL so that plugins can find resources
    2152             Thread.currentThread().setContextClassLoader(classLoader);
    2153             UIManager.setLookAndFeel(lookAndFeelClassName);
    2154             UIManager.getLookAndFeelDefaults().put("ClassLoader", classLoader);
    2155           } catch (Exception e) {
    2156             e.printStackTrace();
    2157           } finally{
    2158               // now switch it back...
    2159               Thread.currentThread().setContextClassLoader(previousTCCL);
    2160           }
    2161   }
    2162
    2163     /**
    2164      * Makes sure that the LoggerRepository has the ChainsawAppenderHandler
    2165      * added to the root logger so Chainsaw can receive all the events.
    2166      */
    2167     private void ensureChainsawAppenderHandlerAdded() {
    2168         if(!LogManager.getLoggerRepository().getRootLogger().isAttached(handler)) {
    2169             LogManager.getLoggerRepository().getRootLogger().addAppender(handler);
    2170         }
    2171     }
    2172
    2173 /**
    2174    * This class handles the recption of the Event batches and creates new
    2175    * LogPanels if the identifier is not in use otherwise it ignores the event
    2176    * batch.
    2177    *
    2178    * @author Paul Smith
    2179    *                &lt;psmith@apache.org&gt;
    2180    *
    2181    */
    2182   private class NewTabEventBatchReceiver implements EventBatchListener {
    2183     /**
    2184          * DOCUMENT ME!
    2185          *
    2186          * @param ident
    2187          * @param events
    2188          */
    2189     public void receiveEventBatch(
    2190       final String ident, final List events) {
    2191       if (events.size() == 0) {
    2192         return;
    2193       }
    2194
    2195       if (!isGUIFullyInitialized) {
    2196         synchronized (initializationLock) {
    2197           while (!isGUIFullyInitialized) {
    2198             System.out.println(
    2199               "Wanting to add a row, but GUI not initialized, waiting...");
    2200
    2201             /**
    2202                          * Lets wait 1 seconds and recheck.
    2203                          */
    2204             try {
    2205               initializationLock.wait(1000);
    2206               logger.debug("waiting for initialization to complete");
    2207             } catch (InterruptedException e) {
    2208             }
    2209           }
    2210           logger.debug("out of system initialization wait loop");
    2211         }
    2212       }
    2213
    2214       if (!getPanelMap().containsKey(ident)) {
    2215           logger.debug("panel " + ident + " does not exist - creating");
    2216         try {
    2217           buildLogPanel(false, ident, events);
    2218         } catch (IllegalArgumentException iae) {
    2219             logger.error("error creating log panel", iae);
    2220           //should not happen - not a custom expression panel
    2221         }
    2222       }
    2223     }
    2224
    2225     /*
    2226          * (non-Javadoc)
    2227          *
    2228          * @see org.apache.log4j.chainsaw.EventBatchListener#getInterestedIdentifier()
    2229          */
    2230
    2231     /**
    2232          * DOCUMENT ME!
    2233          *
    2234          * @return DOCUMENT ME!
    2235          */
    2236     public String getInterestedIdentifier() {
    2237       // we are interested in all batches so we can detect new identifiers
    2238       return null;
    2239     }
    2240   }
    2241
    2242   private class TabIconHandler implements EventCountListener, ChangeListener {
    2243     //the tabIconHandler is associated with a new tab, and a new tab always
    2244     //shows the 'new events' icon
    2245     private boolean newEvents = true;
    2246     private boolean seenEvents = false;
    2247     private final String ident;
    2248     ImageIcon NEW_EVENTS = new ImageIcon(ChainsawIcons.ANIM_RADIO_TOWER);
    2249     ImageIcon HAS_EVENTS = new ImageIcon(ChainsawIcons.INFO);
    2250     Icon SELECTED = LineIconFactory.createBlankIcon();
    2251
    2252     public TabIconHandler(String identifier) {
    2253       ident = identifier;
    2254
    2255       new Thread(
    2256         new Runnable() {
    2257           public void run() {
    2258             while (true) {
    2259               //if this tab is active, remove the icon
    2260               //don't process undocked tabs
    2261               if (getTabbedPane().indexOfTab(ident) > -1 &&
    2262                 getTabbedPane().getSelectedIndex() == getTabbedPane()
    2263                                                           .indexOfTab(ident)) {
    2264                 getTabbedPane().setIconAt(
    2265                   getTabbedPane().indexOfTab(ident), SELECTED);
    2266                 newEvents = false;
    2267                 seenEvents = true;
    2268               } else if (getTabbedPane().indexOfTab(ident) > -1) {
    2269                 if (newEvents) {
    2270                   getTabbedPane().setIconAt(
    2271                     getTabbedPane().indexOfTab(ident), NEW_EVENTS);
    2272                   newEvents = false;
    2273                   seenEvents = false;
    2274                 } else if (!seenEvents) {
    2275                   getTabbedPane().setIconAt(
    2276                     getTabbedPane().indexOfTab(ident), HAS_EVENTS);
    2277                 }
    2278               }
    2279
    2280               try {
    2281                 Thread.sleep(handler.getQueueInterval() + 1000);
    2282               } catch (InterruptedException ie) {
    2283               }
    2284             }
    2285           }
    2286         }).start();
    2287     }
    2288
    2289     /**
    2290          * DOCUMENT ME!
    2291          *
    2292          * @param currentCount
    2293          *                    DOCUMENT ME!
    2294          * @param totalCount
    2295          *                    DOCUMENT ME!
    2296          */
    2297     public void eventCountChanged(int currentCount, int totalCount) {
    2298       newEvents = true;
    2299     }
    2300
    2301     public void stateChanged(ChangeEvent event) {
    2302       if (
    2303         getTabbedPane().indexOfTab(ident) > -1 && getTabbedPane().indexOfTab(ident) == getTabbedPane().getSelectedIndex()) {
    2304         getTabbedPane().setIconAt(getTabbedPane().indexOfTab(ident), SELECTED);
    2305       }
    2306     }
    2307   }
    2308 }