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 <sdeboy@apache.org>
145  * @author Paul Smith  <psmith@apache.org>
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 }