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