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   */
19  package org.apache.log4j.chainsaw;
20  
21  import java.awt.BorderLayout;
22  import java.awt.Color;
23  import java.awt.Component;
24  import java.awt.Cursor;
25  import java.awt.Dimension;
26  import java.awt.EventQueue;
27  import java.awt.Font;
28  import java.awt.Point;
29  import java.awt.Toolkit;
30  import java.awt.event.ActionEvent;
31  import java.awt.event.InputEvent;
32  import java.awt.event.MouseAdapter;
33  import java.awt.event.MouseEvent;
34  import java.awt.event.MouseMotionAdapter;
35  import java.awt.event.MouseMotionListener;
36  import java.beans.PropertyChangeEvent;
37  import java.beans.PropertyChangeListener;
38  import java.util.ArrayList;
39  import java.util.Collection;
40  import java.util.Collections;
41  import java.util.Enumeration;
42  import java.util.HashSet;
43  import java.util.Iterator;
44  import java.util.List;
45  import java.util.Map;
46  import java.util.Set;
47  
48  import javax.swing.AbstractAction;
49  import javax.swing.Action;
50  import javax.swing.BorderFactory;
51  import javax.swing.Box;
52  import javax.swing.BoxLayout;
53  import javax.swing.DefaultListModel;
54  import javax.swing.ImageIcon;
55  import javax.swing.JButton;
56  import javax.swing.JCheckBoxMenuItem;
57  import javax.swing.JColorChooser;
58  import javax.swing.JDialog;
59  import javax.swing.JEditorPane;
60  import javax.swing.JLabel;
61  import javax.swing.JList;
62  import javax.swing.JPanel;
63  import javax.swing.JPopupMenu;
64  import javax.swing.JScrollPane;
65  import javax.swing.JToolBar;
66  import javax.swing.JTree;
67  import javax.swing.SwingUtilities;
68  import javax.swing.ToolTipManager;
69  import javax.swing.UIManager;
70  import javax.swing.event.ChangeEvent;
71  import javax.swing.event.ChangeListener;
72  import javax.swing.event.EventListenerList;
73  import javax.swing.event.TreeModelEvent;
74  import javax.swing.event.TreeModelListener;
75  import javax.swing.event.TreeSelectionEvent;
76  import javax.swing.event.TreeSelectionListener;
77  import javax.swing.tree.DefaultMutableTreeNode;
78  import javax.swing.tree.DefaultTreeCellRenderer;
79  import javax.swing.tree.DefaultTreeSelectionModel;
80  import javax.swing.tree.TreeNode;
81  import javax.swing.tree.TreePath;
82  import javax.swing.tree.TreeSelectionModel;
83  
84  import org.apache.log4j.LogManager;
85  import org.apache.log4j.Logger;
86  import org.apache.log4j.chainsaw.color.RuleColorizer;
87  import org.apache.log4j.chainsaw.filter.FilterModel;
88  import org.apache.log4j.chainsaw.icons.ChainsawIcons;
89  import org.apache.log4j.chainsaw.icons.LineIconFactory;
90  import org.apache.log4j.rule.AbstractRule;
91  import org.apache.log4j.rule.ColorRule;
92  import org.apache.log4j.rule.ExpressionRule;
93  import org.apache.log4j.rule.Rule;
94  import org.apache.log4j.spi.LoggingEvent;
95  
96  
97  /**
98   * A panel that encapsulates the Logger Name tree, with associated actions
99   * and implements the Rule interface so that it can filter in/out events
100  * that do not match the users request for refining the view based on Loggers.
101  *
102  * @author Paul Smith <psmith@apache.org>
103  */
104 final class LoggerNameTreePanel extends JPanel implements LoggerNameListener
105 {
106   //~ Static fields/initializers ==============================================
107 
108   private static final int WARN_DEPTH = 4;
109 
110   //~ Instance fields =========================================================
111 
112   private LoggerNameTreeCellRenderer cellRenderer =
113     new LoggerNameTreeCellRenderer();
114   private final Action clearIgnoreListAction;
115   private final Action closeAction;
116   private final JButton closeButton = new SmallButton();
117   private final Action collapseAction;
118   private final JButton collapseButton = new SmallButton();
119   private final Action editLoggerAction;
120   private final JButton editLoggerButton = new SmallButton();
121   private final Action expandAction;
122   private final Action findAction;
123   private final Action clearFindNextAction;
124   private final Action defineColorRuleForLoggerAction;
125   private final Action setRefineFocusAction;
126   private final Action updateRefineFocusAction;
127   private final Action updateFindAction;
128   private final JButton expandButton = new SmallButton();
129   private final Action focusOnAction;
130   private final Action clearRefineFocusAction;
131   private final SmallToggleButton focusOnLoggerButton =
132     new SmallToggleButton();
133   private final Set hiddenSet = new HashSet();
134   private final Action hideAction;
135   private final Action hideSubLoggersAction;
136   private final LogPanelPreferenceModel preferenceModel;
137 
138   private final JList ignoreList = new JList();
139   private final JEditorPane ignoreExpressionEntryField = new JEditorPane();
140   private final JEditorPane alwaysDisplayExpressionEntryField = new JEditorPane();
141   private final JScrollPane ignoreListScroll = new JScrollPane(ignoreList);
142   private final JDialog ignoreDialog = new JDialog();
143   private final JDialog ignoreExpressionDialog = new JDialog();
144   private final JDialog alwaysDisplayExpressionDialog = new JDialog();
145   private final JLabel ignoreSummary = new JLabel("0 hidden loggers");
146   private final JLabel ignoreExpressionSummary = new JLabel("Ignore expression");
147   private final JLabel alwaysDisplayExpressionSummary = new JLabel("Always displayed expression");
148   private final SmallToggleButton ignoreLoggerButton = new SmallToggleButton();
149   private final EventListenerList listenerList = new EventListenerList();
150   private final JTree logTree;
151   private final Logger logger = LogManager.getLogger(LoggerNameTreePanel.class);
152 
153   //  private final EventListenerList focusOnActionListeners =
154   //    new EventListenerList();
155   private final LogPanelLoggerTreeModel logTreeModel;
156   private final PopupListener popupListener;
157   private final LoggerTreePopupMenu popupMenu;
158   private final VisibilityRuleDelegate visibilityRuleDelegate;
159   private Rule colorRuleDelegate; 
160   private final JScrollPane scrollTree;
161   private final JToolBar toolbar = new JToolBar();
162   private final LogPanel logPanel;
163   private final RuleColorizer colorizer;
164   private Rule ignoreExpressionRule;
165   private Rule alwaysDisplayExpressionRule;
166   private boolean expandRootLatch = false;
167   private String currentlySelectedLoggerName;
168 
169     //~ Constructors ============================================================
170 
171   /**
172    * Creates a new LoggerNameTreePanel object.
173    *
174    * @param logTreeModel
175    */
176   LoggerNameTreePanel(LogPanelLoggerTreeModel logTreeModel, LogPanelPreferenceModel preferenceModel, LogPanel logPanel, RuleColorizer colorizer, FilterModel filterModel)
177   {
178     super();
179     this.logTreeModel = logTreeModel;
180     this.preferenceModel = preferenceModel;
181     this.logPanel = logPanel;
182     this.colorizer = colorizer;
183 
184     setLayout(new BorderLayout());
185     ignoreExpressionEntryField.setPreferredSize(new Dimension(300, 150));
186     alwaysDisplayExpressionEntryField.setPreferredSize(new Dimension(300, 150));
187     alwaysDisplayExpressionSummary.setMinimumSize(new Dimension(10, alwaysDisplayExpressionSummary.getHeight()));
188     ignoreExpressionSummary.setMinimumSize(new Dimension(10, ignoreExpressionSummary.getHeight()));
189     ignoreSummary.setMinimumSize(new Dimension(10, ignoreSummary.getHeight()));
190 
191     JTextComponentFormatter.applySystemFontAndSize(ignoreExpressionEntryField);
192     JTextComponentFormatter.applySystemFontAndSize(alwaysDisplayExpressionEntryField);
193 
194     visibilityRuleDelegate = new VisibilityRuleDelegate();
195     colorRuleDelegate = 
196         new AbstractRule()
197         {
198           public boolean evaluate(LoggingEvent e, Map matches)
199           {
200             boolean hiddenLogger = e.getLoggerName() != null && isHiddenLogger(e.getLoggerName());
201             boolean hiddenExpression = (ignoreExpressionRule != null && ignoreExpressionRule.evaluate(e, null));
202             boolean alwaysDisplayExpression = (alwaysDisplayExpressionRule != null && alwaysDisplayExpressionRule.evaluate(e, null));
203             boolean hidden = (!alwaysDisplayExpression) && (hiddenLogger || hiddenExpression);
204             String currentlySelectedLoggerName = getCurrentlySelectedLoggerName();
205 
206             if (!isFocusOnSelected() && !hidden && currentlySelectedLoggerName != null && !"".equals(currentlySelectedLoggerName))
207             {
208             	return (e.getLoggerName().startsWith(currentlySelectedLoggerName+".") || e.getLoggerName().endsWith(currentlySelectedLoggerName)) ;
209             }
210             return false;
211           }
212         };
213 
214     logTree =
215     new JTree(logTreeModel)
216       {
217         public String getToolTipText(MouseEvent ev)
218         {
219           if (ev == null)
220           {
221             return null;
222           }
223 
224           TreePath path = logTree.getPathForLocation(ev.getX(), ev.getY());
225 
226           String loggerName = getLoggerName(path);
227 
228           if (hiddenSet.contains(loggerName))
229           {
230             loggerName += " (you are ignoring this logger)";
231           }
232 
233           return loggerName;
234         }
235       };
236 
237     ToolTipManager.sharedInstance().registerComponent(logTree);
238     logTree.setCellRenderer(cellRenderer);
239 
240     //	============================================
241     logTreeModel.addTreeModelListener(new TreeModelListener()
242       {
243         public void treeNodesChanged(TreeModelEvent e)
244         {
245         }
246 
247         public void treeNodesInserted(TreeModelEvent e)
248         {
249           if (!expandRootLatch)
250           {
251             ensureRootExpanded();
252             expandRootLatch = true;
253           }
254         }
255 
256         public void treeNodesRemoved(TreeModelEvent e)
257         {
258         }
259 
260         public void treeStructureChanged(TreeModelEvent e)
261         {
262         }
263       });
264 
265     logTree.setEditable(false);
266 
267     //	TODO decide if Multi-selection is useful, and how it would work	
268     TreeSelectionModel selectionModel = new DefaultTreeSelectionModel();
269     selectionModel.setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
270     logTree.setSelectionModel(selectionModel);
271 
272     logTree.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
273     scrollTree = new JScrollPane(logTree);
274     toolbar.setLayout(new BoxLayout(toolbar, BoxLayout.X_AXIS));
275 
276     expandAction = createExpandAction();
277     findAction = createFindNextAction();
278     clearFindNextAction = createClearFindNextAction();
279     defineColorRuleForLoggerAction = createDefineColorRuleForLoggerAction();
280     clearRefineFocusAction = createClearRefineFocusAction();
281     setRefineFocusAction = createSetRefineFocusAction();
282     updateRefineFocusAction = createUpdateRefineFocusAction();
283     updateFindAction = createUpdateFindAction();
284     editLoggerAction = createEditLoggerAction();
285     closeAction = createCloseAction();
286     collapseAction = createCollapseAction();
287     focusOnAction = createFocusOnAction();
288     hideAction = createIgnoreAction();
289     hideSubLoggersAction = createIgnoreAllAction();
290     clearIgnoreListAction = createClearIgnoreListAction();
291 
292     popupMenu = new LoggerTreePopupMenu();
293     popupListener = new PopupListener(popupMenu);
294 
295     setupListeners();
296     configureToolbarPanel();
297 
298     add(toolbar, BorderLayout.NORTH);
299     add(scrollTree, BorderLayout.CENTER);
300 
301     ignoreDialog.setTitle("Hidden/Ignored Loggers");
302     ignoreDialog.setModal(true);
303 
304     ignoreExpressionDialog.setTitle("Hidden/Ignored Expression");
305     ignoreExpressionDialog.setModal(true);
306 
307     alwaysDisplayExpressionDialog.setTitle("Always displayed Expression");
308     alwaysDisplayExpressionDialog.setModal(true);
309 
310     JPanel ignorePanel = new JPanel();
311     ignorePanel.setLayout(new BoxLayout(ignorePanel, BoxLayout.Y_AXIS));
312     JPanel ignoreSummaryPanel = new JPanel();
313     ignoreSummaryPanel.setLayout(new BoxLayout(ignoreSummaryPanel, BoxLayout.X_AXIS));
314     ignoreSummaryPanel.add(ignoreSummary);
315     
316     Action showIgnoreDialogAction = new AbstractAction("...") {
317         public void actionPerformed(ActionEvent e) {
318             LogPanel.centerAndSetVisible(ignoreDialog);
319         }
320     };
321 
322     Action showIgnoreExpressionDialogAction = new AbstractAction("...") {
323       public void actionPerformed(ActionEvent e) {
324           LogPanel.centerAndSetVisible(ignoreExpressionDialog);
325       }
326     };
327 
328 
329     Action showAlwaysDisplayExpressionDialogAction = new AbstractAction("...") {
330       public void actionPerformed(ActionEvent e) {
331           LogPanel.centerAndSetVisible(alwaysDisplayExpressionDialog);
332       }
333     };
334 
335     showIgnoreDialogAction.putValue(Action.SHORT_DESCRIPTION, "Click to view and manage your hidden/ignored loggers");
336     JButton btnShowIgnoreDialog = new SmallButton(showIgnoreDialogAction);
337     
338     ignoreSummaryPanel.add(btnShowIgnoreDialog);
339     ignorePanel.add(ignoreSummaryPanel);
340 
341     JPanel ignoreExpressionPanel = new JPanel();
342     ignoreExpressionPanel.setLayout(new BoxLayout(ignoreExpressionPanel, BoxLayout.X_AXIS));
343     ignoreExpressionPanel.add(ignoreExpressionSummary);
344     showIgnoreExpressionDialogAction.putValue(Action.SHORT_DESCRIPTION, "Click to view and manage your hidden/ignored expression");
345     JButton btnShowIgnoreExpressionDialog = new SmallButton(showIgnoreExpressionDialogAction);
346     ignoreExpressionPanel.add(btnShowIgnoreExpressionDialog);
347 
348     ignorePanel.add(ignoreExpressionPanel);
349 
350     JPanel alwaysDisplayExpressionPanel = new JPanel();
351     alwaysDisplayExpressionPanel.setLayout(new BoxLayout(alwaysDisplayExpressionPanel, BoxLayout.X_AXIS));
352     alwaysDisplayExpressionPanel.add(alwaysDisplayExpressionSummary);
353     showAlwaysDisplayExpressionDialogAction.putValue(Action.SHORT_DESCRIPTION, "Click to view and manage your always-displayed expression");
354     JButton btnShowAlwaysDisplayExpressionDialog = new SmallButton(showAlwaysDisplayExpressionDialogAction);
355     alwaysDisplayExpressionPanel.add(btnShowAlwaysDisplayExpressionDialog);
356 
357     ignorePanel.add(alwaysDisplayExpressionPanel);
358 
359     add(ignorePanel, BorderLayout.SOUTH);
360 
361     ignoreList.setModel(new DefaultListModel());
362     ignoreList.addMouseListener(new MouseAdapter()
363       {
364         public void mouseClicked(MouseEvent e)
365         {
366           if (
367             (e.getClickCount() > 1)
368               && ((e.getModifiers() & InputEvent.BUTTON1_MASK) > 0))
369           {
370             int index = ignoreList.locationToIndex(e.getPoint());
371 
372             if (index >= 0)
373             {
374               String string =
375                 ignoreList.getModel().getElementAt(index).toString();
376               toggleHiddenLogger(string);
377               fireChangeEvent();
378 
379               /**
380                * TODO this needs to get the node that has this logger and fire a visual update
381                */
382               LoggerNameTreePanel.this.logTreeModel.nodeStructureChanged(
383                 (TreeNode) LoggerNameTreePanel.this.logTreeModel.getRoot());
384             }
385           }
386         }
387       });
388     
389     JPanel ignoreListPanel = new JPanel(new BorderLayout());
390     ignoreListScroll.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(),"Double click an entry to unhide it"));
391     ignoreListPanel.add(ignoreListScroll, BorderLayout.CENTER);
392 
393     JPanel ignoreExpressionDialogPanel = new JPanel(new BorderLayout());
394     ignoreExpressionEntryField.addKeyListener(new ExpressionRuleContext(filterModel, ignoreExpressionEntryField));
395 
396     ignoreExpressionDialogPanel.add(new JScrollPane(ignoreExpressionEntryField), BorderLayout.CENTER);
397     JButton ignoreExpressionCloseButton = new JButton(new AbstractAction(" Close ") {
398           public void actionPerformed(ActionEvent e)
399           {
400               String ignoreText = ignoreExpressionEntryField.getText();
401 
402               if (updateIgnoreExpression(ignoreText)) {
403                 ignoreExpressionDialog.setVisible(false);
404               }
405           }});
406 
407 
408     JPanel alwaysDisplayExpressionDialogPanel = new JPanel(new BorderLayout());
409     alwaysDisplayExpressionEntryField.addKeyListener(new ExpressionRuleContext(filterModel, alwaysDisplayExpressionEntryField));
410 
411     alwaysDisplayExpressionDialogPanel.add(new JScrollPane(alwaysDisplayExpressionEntryField), BorderLayout.CENTER);
412     JButton alwaysDisplayExpressionCloseButton = new JButton(new AbstractAction(" Close ") {
413           public void actionPerformed(ActionEvent e)
414           {
415               String alwaysDisplayText = alwaysDisplayExpressionEntryField.getText();
416 
417               if (updateAlwaysDisplayExpression(alwaysDisplayText)) {
418                 alwaysDisplayExpressionDialog.setVisible(false);
419               }
420           }});
421 
422     JPanel closeAlwaysDisplayExpressionPanel = new JPanel();
423     closeAlwaysDisplayExpressionPanel.add(alwaysDisplayExpressionCloseButton);
424     alwaysDisplayExpressionDialogPanel.add(closeAlwaysDisplayExpressionPanel, BorderLayout.SOUTH);
425 
426     JPanel closeIgnoreExpressionPanel = new JPanel();
427     closeIgnoreExpressionPanel.add(ignoreExpressionCloseButton);
428     ignoreExpressionDialogPanel.add(closeIgnoreExpressionPanel, BorderLayout.SOUTH);
429 
430     Box ignoreListButtonPanel = Box.createHorizontalBox();
431     
432     JButton unhideAll = new JButton(new AbstractAction(" Unhide All ") {
433 
434         public void actionPerformed(final ActionEvent e)
435         {
436              SwingUtilities.invokeLater(new Runnable() {
437 
438                 public void run()
439                 {
440                     clearIgnoreListAction.actionPerformed(e);
441                 }}); 
442             
443         }});
444     ignoreListButtonPanel.add(unhideAll);
445     
446     ignoreListButtonPanel.add(Box.createHorizontalGlue());
447     JButton ignoreCloseButton = new JButton(new AbstractAction(" Close ") {
448 
449         public void actionPerformed(ActionEvent e)
450         {
451             ignoreDialog.setVisible(false);
452             
453         }});
454     ignoreListButtonPanel.add(ignoreCloseButton);
455     
456     
457     ignoreListPanel.add(ignoreListButtonPanel, BorderLayout.SOUTH);
458     
459     
460     ignoreDialog.getContentPane().add(ignoreListPanel);
461     ignoreDialog.pack();
462 
463     ignoreExpressionDialog.getContentPane().add(ignoreExpressionDialogPanel);
464     ignoreExpressionDialog.pack();
465 
466     alwaysDisplayExpressionDialog.getContentPane().add(alwaysDisplayExpressionDialogPanel);
467     alwaysDisplayExpressionDialog.pack();
468   }
469 
470     private boolean updateIgnoreExpression(String ignoreText)
471     {
472         try {
473             if (ignoreText != null && !ignoreText.trim().equals("")) {
474                 ignoreExpressionRule = ExpressionRule.getRule(ignoreText);
475             } else {
476                 ignoreExpressionRule = null;
477             }
478             visibilityRuleDelegate.firePropertyChange("hiddenSet", null, null);
479 
480             updateDisplay();
481             ignoreExpressionEntryField.setBackground(UIManager.getColor("TextField.background"));
482             return true;
483         } catch (IllegalArgumentException iae) {
484             ignoreExpressionEntryField.setToolTipText(iae.getMessage());
485             ignoreExpressionEntryField.setBackground(ChainsawConstants.INVALID_EXPRESSION_BACKGROUND);
486             return false;
487         }
488     }
489 
490     private boolean updateAlwaysDisplayExpression(String alwaysDisplayText)
491     {
492         try {
493             if (alwaysDisplayText != null && !alwaysDisplayText.trim().equals("")) {
494                 alwaysDisplayExpressionRule = ExpressionRule.getRule(alwaysDisplayText);
495             } else {
496                 alwaysDisplayExpressionRule = null;
497             }
498             visibilityRuleDelegate.firePropertyChange("alwaysDisplayedSet", null, null);
499 
500             updateDisplay();
501             alwaysDisplayExpressionEntryField.setBackground(UIManager.getColor("TextField.background"));
502             return true;
503         } catch (IllegalArgumentException iae) {
504             alwaysDisplayExpressionEntryField.setToolTipText(iae.getMessage());
505             alwaysDisplayExpressionEntryField.setBackground(ChainsawConstants.INVALID_EXPRESSION_BACKGROUND);
506             return false;
507         }
508     }
509 
510     //~ Methods =================================================================
511 
512   /**
513    * Adds a change Listener to this LoggerNameTreePanel to be notfied
514    * when the State of the Focus or Hidden details have changed.
515    *
516    * @param l
517    */
518   public void addChangeListener(ChangeListener l)
519   {
520     listenerList.add(ChangeListener.class, l);
521   }
522 
523   public Rule getLoggerColorRule() {
524   	return colorRuleDelegate;
525   }
526 
527   public Rule getLoggerVisibilityRule() {
528       return visibilityRuleDelegate;
529   }
530 
531   /**
532    * DOCUMENT ME!
533    *
534    * @param l DOCUMENT ME!
535    */
536   public void removeChangeListener(ChangeListener l)
537   {
538     listenerList.remove(ChangeListener.class, l);
539   }
540 
541   /**
542    * Ensures the Focus is set to a specific logger name
543    * @param
544    */
545   public void setFocusOn(String newLogger)
546   {
547     DefaultMutableTreeNode node = logTreeModel.lookupLogger(newLogger);
548 
549     if (node != null)
550     {
551       TreeNode[] nodes = node.getPath();
552       TreePath treePath = new TreePath(nodes);
553       logTree.setSelectionPath(treePath);
554 
555       if (!focusOnLoggerButton.isSelected())
556       {
557         focusOnLoggerButton.doClick();
558       }
559     }
560     else
561     {
562       logger.error("failed to lookup logger " + newLogger);
563     }
564   }
565 
566   private boolean isHiddenLogger(String loggerName) {
567     for (Iterator iter = hiddenSet.iterator();iter.hasNext();) {
568       String hiddenLoggerEntry = iter.next().toString();
569       if (loggerName.startsWith(hiddenLoggerEntry + ".") || loggerName.endsWith(hiddenLoggerEntry)) {
570           return true;
571       }
572     }
573     return false;
574   }
575 
576 
577 
578   /**
579    * DOCUMENT ME!
580    *
581    * @param logger
582    */
583   protected void toggleHiddenLogger(String logger)
584   {
585     if (!hiddenSet.contains(logger))
586     {
587       hiddenSet.add(logger);
588     }
589     else
590     {
591       hiddenSet.remove(logger);
592     }
593 
594     visibilityRuleDelegate.firePropertyChange("hiddenSet", (Object) null, (Object) null);
595   }
596 
597   /**
598    * Returns the full name of the Logger that is represented by
599    * the currently selected Logger node in the tree.
600    *
601    * This is the dotted name, of the current node including all it's parents.
602    *
603    * If multiple Nodes are selected, the first path is used
604    * @return Logger Name or null if nothing selected
605    */
606   String getCurrentlySelectedLoggerName()
607   {
608     TreePath[] paths = logTree.getSelectionPaths();
609 
610     if ((paths == null) || (paths.length == 0))
611     {
612       return null;
613     }
614 
615     TreePath firstPath = paths[0];
616 
617     return getLoggerName(firstPath);
618   }
619 
620   /**
621    * Returns the full
622    * @param path DOCUMENT ME!
623    * @return
624    */
625   String getLoggerName(TreePath path)
626   {
627     if (path != null)
628     {
629       Object[] objects = path.getPath();
630       StringBuffer buf = new StringBuffer();
631 
632       for (int i = 1; i < objects.length; i++)
633       {
634         buf.append(objects[i].toString());
635 
636         if (i < (objects.length - 1))
637         {
638           buf.append(".");
639         }
640       }
641 
642       return buf.toString();
643     }
644 
645     return null;
646   }
647 
648   /**
649    * adds a Collection of Strings to the ignore List and notifise all listeners of
650    * both the "hiddenSet" property and those expecting the Rule to change
651    * via the ChangeListener interface 
652    * @param fqnLoggersToIgnore
653    */
654   void ignore(Collection fqnLoggersToIgnore)
655   {
656     hiddenSet.addAll(fqnLoggersToIgnore);
657     visibilityRuleDelegate.firePropertyChange("hiddenSet", null, null);
658     fireChangeEvent();
659   }
660 
661   /**
662    * Returns true if the FocusOn element has been selected
663    * @return true if the FocusOn action/lement has been selected
664    */
665   boolean isFocusOnSelected()
666   {
667     return focusOnAction.getValue("checked") != null;
668   }
669 
670   void setFocusOnSelected(boolean selected)
671   {
672     if (selected)
673     {
674       focusOnAction.putValue("checked", Boolean.TRUE);
675     }
676     else
677     {
678       focusOnAction.putValue("checked", null);
679     }
680   }
681 
682   /**
683    * Given the currently selected nodes
684    * collapses all the children of those nodes.
685    *
686    */
687   private void collapseCurrentlySelectedNode()
688   {
689     TreePath[] paths = logTree.getSelectionPaths();
690 
691     if (paths == null)
692     {
693       return;
694     }
695 
696       logger.debug("Collapsing all children of selected node");
697 
698     for (int i = 0; i < paths.length; i++)
699     {
700       TreePath path = paths[i];
701       DefaultMutableTreeNode node =
702         (DefaultMutableTreeNode) path.getLastPathComponent();
703       Enumeration enumeration = node.depthFirstEnumeration();
704 
705       while (enumeration.hasMoreElements())
706       {
707         DefaultMutableTreeNode child =
708           (DefaultMutableTreeNode) enumeration.nextElement();
709 
710         if ((child.getParent() != null) && (child != node))
711         {
712           TreeNode[] nodes =
713             ((DefaultMutableTreeNode) child.getParent()).getPath();
714 
715           TreePath treePath = new TreePath(nodes);
716           logTree.collapsePath(treePath);
717         }
718       }
719     }
720 
721     ensureRootExpanded();
722   }
723 
724   /**
725      * configures all the components that are used in the mini-toolbar of this
726      * component
727      */
728   private void configureToolbarPanel()
729   {
730     toolbar.setFloatable(false);
731 
732     expandButton.setAction(expandAction);
733     expandButton.setText(null);
734     collapseButton.setAction(collapseAction);
735     collapseButton.setText(null);
736     focusOnLoggerButton.setAction(focusOnAction);
737     focusOnLoggerButton.setText(null);
738     ignoreLoggerButton.setAction(hideAction);
739     ignoreLoggerButton.setText(null);
740 
741     expandButton.setFont(expandButton.getFont().deriveFont(Font.BOLD));
742     collapseButton.setFont(collapseButton.getFont().deriveFont(Font.BOLD));
743 
744     editLoggerButton.setAction(editLoggerAction);
745     editLoggerButton.setText(null);
746     closeButton.setAction(closeAction);
747     closeButton.setText(null);
748 
749     toolbar.add(expandButton);
750     toolbar.add(collapseButton);
751     toolbar.addSeparator();
752     toolbar.add(focusOnLoggerButton);
753     toolbar.add(ignoreLoggerButton);
754 
755     //    toolbar.add(editLoggerButton);
756     toolbar.addSeparator();
757 
758     toolbar.add(Box.createHorizontalGlue());
759     toolbar.add(closeButton);
760     toolbar.add(Box.createHorizontalStrut(5));
761   }
762 
763   /**
764    * DOCUMENT ME!
765    *
766    * @return
767   */
768   private Action createClearIgnoreListAction()
769   {
770     Action action = new AbstractAction("Clear Ignore list", null)
771       {
772         public void actionPerformed(ActionEvent e)
773         {
774           ignoreLoggerButton.setSelected(false);
775           logTreeModel.reload();
776           hiddenSet.clear();
777           fireChangeEvent();
778         }
779       };
780 
781     action.putValue(
782       Action.SHORT_DESCRIPTION,
783       "Removes all entries from the Ignore list so you can see their events in the view");
784 
785     return action;
786   }
787 
788   /**
789      * An action that closes (hides) this panel
790     * @return
791     */
792   private Action createCloseAction()
793   {
794     Action action = new AbstractAction()
795       {
796         public void actionPerformed(ActionEvent e)
797         {
798             preferenceModel.setLogTreePanelVisible(false);
799         }
800       };
801 
802     action.putValue(Action.NAME, "Close");
803     action.putValue(Action.SHORT_DESCRIPTION, "Closes the Logger panel");
804     action.putValue(Action.SMALL_ICON, LineIconFactory.createCloseIcon());
805 
806     return action;
807   }
808 
809   /**
810    * DOCUMENT ME!
811    *
812    * @return
813   */
814   private Action createCollapseAction()
815   {
816     Action action = new AbstractAction()
817       {
818         public void actionPerformed(ActionEvent e)
819         {
820           collapseCurrentlySelectedNode();
821         }
822       };
823 
824     action.putValue(Action.SMALL_ICON, LineIconFactory.createCollapseIcon());
825     action.putValue(Action.NAME, "Collapse Branch");
826     action.putValue(
827       Action.SHORT_DESCRIPTION,
828       "Collapses all the children of the currently selected node");
829     action.setEnabled(false);
830 
831     return action;
832   }
833 
834   private Action createEditLoggerAction()
835   {
836     Action action = new AbstractAction()
837       {
838         public void actionPerformed(ActionEvent e)
839         {
840           // TODO Auto-generated method stub
841         }
842       };
843 
844     //    TODO enable this when it's ready.
845     action.putValue("enabled", Boolean.FALSE);
846 
847     action.putValue(Action.NAME, "Edit filters/colors");
848     action.putValue(
849       Action.SHORT_DESCRIPTION,
850       "Allows you to specify filters and coloring for this Logger");
851     action.putValue(
852       Action.SMALL_ICON, new ImageIcon(ChainsawIcons.ICON_EDIT_RECEIVER));
853     action.setEnabled(false);
854 
855     return action;
856   }
857 
858   /**
859    * Creates an action that is used to expand the selected node
860    * and all children
861    * @return an Action
862    */
863   private Action createExpandAction()
864   {
865     Action action = new AbstractAction()
866       {
867         public void actionPerformed(ActionEvent e)
868         {
869           expandCurrentlySelectedNode();
870         }
871       };
872 
873     action.putValue(Action.SMALL_ICON, LineIconFactory.createExpandIcon());
874     action.putValue(Action.NAME, "Expand branch");
875     action.putValue(
876       Action.SHORT_DESCRIPTION,
877       "Expands all the child nodes of the currently selected node, recursively");
878     action.setEnabled(false);
879 
880     return action;
881   }
882 
883     /**
884      * Creates an action that is used to find the next match of the selected node (similar to default selection behavior
885      * except the search field is populated and the next match is selected.
886      * @return an Action
887      */
888     private Action createFindNextAction()
889     {
890       Action action = new AbstractAction()
891         {
892           public void actionPerformed(ActionEvent e)
893           {
894             findNextUsingCurrentlySelectedNode();
895           }
896         };
897 
898       action.putValue(Action.NAME, "Find next");
899       action.putValue(
900         Action.SHORT_DESCRIPTION,
901         "Find using the selected node");
902       action.setEnabled(false);
903 
904       return action;
905     }
906 
907     private Action createSetRefineFocusAction()
908     {
909       Action action = new AbstractAction()
910         {
911           public void actionPerformed(ActionEvent e)
912           {
913             setRefineFocusUsingCurrentlySelectedNode();
914           }
915         };
916 
917       action.putValue(Action.NAME, "Set 'refine focus' to selected logger");
918       action.putValue(
919         Action.SHORT_DESCRIPTION,
920         "Refine focus on the selected node");
921       action.setEnabled(false);
922 
923       return action;
924     }
925 
926     private Action createUpdateRefineFocusAction()
927     {
928       Action action = new AbstractAction()
929         {
930           public void actionPerformed(ActionEvent e)
931           {
932             updateRefineFocusUsingCurrentlySelectedNode();
933           }
934         };
935 
936       action.putValue(Action.NAME, "Update 'refine focus' to include selected logger");
937       action.putValue(
938         Action.SHORT_DESCRIPTION,
939         "Add selected node to 'refine focus' field");
940       action.setEnabled(false);
941 
942       return action;
943     }
944 
945   private Action createUpdateFindAction()
946   {
947     Action action = new AbstractAction()
948       {
949         public void actionPerformed(ActionEvent e)
950         {
951           updateFindUsingCurrentlySelectedNode();
952         }
953       };
954 
955     action.putValue(Action.NAME, "Update 'find' to include selected logger");
956     action.putValue(
957       Action.SHORT_DESCRIPTION,
958       "Add selected node to 'find' field");
959     action.setEnabled(false);
960 
961     return action;
962   }
963 
964   private void updateFindUsingCurrentlySelectedNode()
965   {
966       String selectedLogger = getCurrentlySelectedLoggerName();
967       TreePath[] paths = logTree.getSelectionPaths();
968 
969       if (paths == null)
970       {
971         return;
972       }
973       String currentFindText = logPanel.getFindText();
974       logPanel.setFindText(currentFindText + " || logger ~= " + selectedLogger);
975   }
976 
977     private void updateRefineFocusUsingCurrentlySelectedNode()
978     {
979         String selectedLogger = getCurrentlySelectedLoggerName();
980         TreePath[] paths = logTree.getSelectionPaths();
981 
982         if (paths == null)
983         {
984           return;
985         }
986         String currentFilterText = logPanel.getRefineFocusText();
987         logPanel.setRefineFocusText(currentFilterText + " || logger ~= " + selectedLogger);
988     }
989 
990     private void setRefineFocusUsingCurrentlySelectedNode()
991     {
992         String selectedLogger = getCurrentlySelectedLoggerName();
993         TreePath[] paths = logTree.getSelectionPaths();
994 
995         if (paths == null)
996         {
997           return;
998         }
999         logPanel.setRefineFocusText("logger ~= " + selectedLogger);
1000     }
1001 
1002     private Action createDefineColorRuleForLoggerAction() {
1003         Action action = new AbstractAction()
1004           {
1005             public void actionPerformed(ActionEvent e)
1006             {
1007                 String selectedLogger = getCurrentlySelectedLoggerName();
1008                 TreePath[] paths = logTree.getSelectionPaths();
1009 
1010                 if (paths == null)
1011                 {
1012                   return;
1013                 }
1014             Color c = JColorChooser.showDialog(getRootPane(), "Choose a color", Color.red);
1015             if (c != null) {
1016                 String expression = "logger like '^" + selectedLogger + ".*'";
1017                 colorizer.addRule(ChainsawConstants.DEFAULT_COLOR_RULE_NAME, new ColorRule(expression,
1018                         ExpressionRule.getRule(expression), c, ChainsawConstants.COLOR_DEFAULT_FOREGROUND));
1019             }
1020         }};
1021 
1022         action.putValue(Action.NAME, "Define color rule for selected logger");
1023         action.putValue(
1024           Action.SHORT_DESCRIPTION,
1025           "Define color rule for logger");
1026         action.setEnabled(false);
1027         return action;
1028     }
1029 
1030     /**
1031      * Creates an action that is used to find the next match of the selected node (similar to default selection behavior
1032      * except the search field is populated and the next match is selected.
1033      * @return an Action
1034      */
1035     private Action createClearFindNextAction()
1036     {
1037       Action action = new AbstractAction()
1038         {
1039           public void actionPerformed(ActionEvent e)
1040           {
1041             clearFindNext();
1042           }
1043         };
1044 
1045       action.putValue(Action.NAME, "Clear find field");
1046       action.putValue(
1047         Action.SHORT_DESCRIPTION,
1048         "Clear the find field");
1049       action.setEnabled(false);
1050 
1051       return action;
1052     }
1053 
1054     private Action createClearRefineFocusAction()
1055     {
1056       Action action = new AbstractAction()
1057         {
1058           public void actionPerformed(ActionEvent e)
1059           {
1060             clearRefineFocus();
1061           }
1062         };
1063 
1064       action.putValue(Action.NAME, "Clear 'refine focus' field");
1065       action.putValue(
1066         Action.SHORT_DESCRIPTION,
1067         "Clear the refine focus field");
1068       action.setEnabled(false);
1069 
1070       return action;
1071     }
1072 
1073   /**
1074    * DOCUMENT ME!
1075    *
1076    * @return
1077   */
1078   private Action createFocusOnAction()
1079   {
1080     final Action action = new AbstractAction()
1081       {
1082         public void actionPerformed(ActionEvent e)
1083         {
1084           toggleFocusOnState();
1085         }
1086       };
1087 
1088     action.putValue(Action.NAME, "Focus");
1089     action.putValue(
1090       Action.SHORT_DESCRIPTION,
1091       "Allows you to Focus on the selected logger by setting a filter that discards all but this Logger");
1092     action.putValue(
1093       Action.SMALL_ICON, new ImageIcon(ChainsawIcons.WINDOW_ICON));
1094 
1095     action.setEnabled(false);
1096 
1097     return action;
1098   }
1099 
1100     /**
1101      * DOCUMENT ME!
1102      *
1103      * @return
1104       */
1105     private Action createIgnoreAllAction()
1106     {
1107       Action action =
1108         new AbstractAction(
1109           "Ignore loggers below selection")
1110         {
1111           public void actionPerformed(ActionEvent e)
1112           {
1113             //add all top level loggers as hidden loggers
1114               TreePath[] paths = logTree.getSelectionPaths();
1115 
1116               String parentPathString = "";
1117               DefaultMutableTreeNode root;
1118               if ((paths == null) || (paths.length == 0))
1119               {
1120                   root = (DefaultMutableTreeNode) logTreeModel.getRoot();
1121               } else {
1122                   root = (DefaultMutableTreeNode) logTree.getSelectionPath().getLastPathComponent();
1123                   TreeNode[] path = root.getPath();
1124                   //don't add 'root logger' to path string
1125                   for (int i=1;i<path.length;i++) {
1126                       if (i > 1) {
1127                           parentPathString = parentPathString + ".";
1128                       }
1129                       parentPathString = parentPathString + path[i].toString();
1130                   }
1131                   if (!(parentPathString.equals(""))) {
1132                       parentPathString = parentPathString + ".";
1133                   }
1134               }
1135               Enumeration topLevelLoggersEnumeration = root.children();
1136               Set topLevelLoggersSet = new HashSet();
1137               while (topLevelLoggersEnumeration.hasMoreElements()) {
1138                   String thisLogger = topLevelLoggersEnumeration.nextElement().toString();
1139                   topLevelLoggersSet.add(parentPathString + thisLogger);
1140               }
1141               if (topLevelLoggersSet.size() > 0) {
1142                   ignore(topLevelLoggersSet);
1143               }
1144 
1145               logTreeModel.nodeChanged(root);
1146               ignoreLoggerButton.setSelected(false);
1147               focusOnAction.setEnabled(false);
1148               popupMenu.hideCheck.setSelected(false);
1149               fireChangeEvent();
1150           }
1151         };
1152 
1153       action.putValue(
1154         Action.SHORT_DESCRIPTION,
1155         "Adds all loggers to your Ignore list (unhide loggers you want to see in the view)");
1156 
1157       return action;
1158     }
1159 
1160   /**
1161    * DOCUMENT ME!
1162    *
1163    * @return
1164     */
1165   private Action createIgnoreAction()
1166   {
1167     Action action =
1168       new AbstractAction(
1169         "Ignore this Logger", new ImageIcon(ChainsawIcons.ICON_COLLAPSE))
1170       {
1171         public void actionPerformed(ActionEvent e)
1172         {
1173           String logger = getCurrentlySelectedLoggerName();
1174 
1175           if (logger != null)
1176           {
1177             toggleHiddenLogger(logger);
1178             logTreeModel.nodeChanged(
1179               (TreeNode) logTree.getSelectionPath().getLastPathComponent());
1180             ignoreLoggerButton.setSelected(hiddenSet.contains(logger));
1181             focusOnAction.setEnabled(!hiddenSet.contains(logger));
1182             popupMenu.hideCheck.setSelected(hiddenSet.contains(logger));
1183           }
1184 
1185           fireChangeEvent();
1186         }
1187       };
1188 
1189     action.putValue(
1190       Action.SHORT_DESCRIPTION,
1191       "Adds the selected Logger to your Ignore list, filtering those events from view");
1192 
1193     return action;
1194   }
1195 
1196   private void ensureRootExpanded()
1197   {
1198       logger.debug("Ensuring Root node is expanded.");
1199 
1200     final DefaultMutableTreeNode root =
1201       (DefaultMutableTreeNode) logTreeModel.getRoot();
1202     SwingUtilities.invokeLater(new Runnable()
1203       {
1204         public void run()
1205         {
1206           logTree.expandPath(new TreePath(root));
1207         }
1208       });
1209   }
1210 
1211   private void findNextUsingCurrentlySelectedNode()
1212   {
1213       String selectedLogger = getCurrentlySelectedLoggerName();
1214       TreePath[] paths = logTree.getSelectionPaths();
1215 
1216       if (paths == null)
1217       {
1218         return;
1219       }
1220       logPanel.setFindText("logger like '^" + selectedLogger + ".*'");
1221   }
1222 
1223   private void clearFindNext()
1224   {
1225       logPanel.setFindText("");
1226   }
1227 
1228   private void clearRefineFocus()
1229   {
1230       logPanel.setRefineFocusText("");
1231   }
1232 
1233   /**
1234    * Expands the currently selected node (if any)
1235    * including all the children.
1236    *
1237    */
1238   private void expandCurrentlySelectedNode()
1239   {
1240     TreePath[] paths = logTree.getSelectionPaths();
1241 
1242     if (paths == null)
1243     {
1244       return;
1245     }
1246 
1247       logger.debug("Expanding all children of selected node");
1248 
1249     for (int i = 0; i < paths.length; i++)
1250     {
1251       TreePath path = paths[i];
1252 
1253       /**
1254        * TODO this is commented out, right now it expands all nodes including the root, so if there is a large tree..... look out.
1255        */
1256 
1257       //      /**
1258       //       * Handle an expansion of the Root node by only doing the first level.
1259       //       * Safe...
1260       //       */
1261       //      if (path.getPathCount() == 1) {
1262       //        logTree.expandPath(path);
1263       //
1264       //        return;
1265       //      }
1266 
1267       DefaultMutableTreeNode treeNode =
1268         (DefaultMutableTreeNode) path.getLastPathComponent();
1269 
1270       Enumeration depthEnum = treeNode.depthFirstEnumeration();
1271 
1272       if (!depthEnum.hasMoreElements())
1273       {
1274         break;
1275       }
1276 
1277       List depths = new ArrayList();
1278 
1279       while (depthEnum.hasMoreElements())
1280       {
1281         depths.add(
1282           new Integer(
1283             ((DefaultMutableTreeNode) depthEnum.nextElement()).getDepth()));
1284       }
1285 
1286       Collections.sort(depths);
1287       Collections.reverse(depths);
1288 
1289       int maxDepth = ((Integer) depths.get(0)).intValue();
1290 
1291       if (maxDepth > WARN_DEPTH)
1292       {
1293         logger.warn("Should warn user, depth=" + maxDepth);
1294       }
1295 
1296       depthEnum = treeNode.depthFirstEnumeration();
1297 
1298       while (depthEnum.hasMoreElements())
1299       {
1300         DefaultMutableTreeNode node =
1301           (DefaultMutableTreeNode) depthEnum.nextElement();
1302 
1303         if (node.isLeaf() && node.getParent() != null)
1304         {
1305           TreeNode[] nodes =
1306             ((DefaultMutableTreeNode) node.getParent()).getPath();
1307           TreePath treePath = new TreePath(nodes);
1308 
1309           logger.debug("Expanding path:" + treePath);
1310 
1311           logTree.expandPath(treePath);
1312         }
1313       }
1314     }
1315   }
1316 
1317   private void fireChangeEvent()
1318   {
1319     ChangeListener[] listeners =
1320       (ChangeListener[]) listenerList.getListeners(ChangeListener.class);
1321     ChangeEvent e = null;
1322 
1323     for (int i = 0; i < listeners.length; i++)
1324     {
1325       if (e == null)
1326       {
1327         e = new ChangeEvent(this);
1328       }
1329 
1330       listeners[i].stateChanged(e);
1331     }
1332   }
1333 
1334   private void reconfigureMenuText()
1335   {
1336     String logger = getCurrentlySelectedLoggerName();
1337 
1338     if ((logger == null) || (logger.length() == 0))
1339     {
1340       focusOnAction.putValue(Action.NAME, "Focus On...");
1341       hideAction.putValue(Action.NAME, "Ignore...");
1342       findAction.putValue(Action.NAME, "Find...");
1343       setRefineFocusAction.putValue(Action.NAME, "Set refine focus field");
1344       updateRefineFocusAction.putValue(Action.NAME, "Add to refine focus field");
1345       updateFindAction.putValue(Action.NAME, "Add to find field");
1346       defineColorRuleForLoggerAction.putValue(Action.NAME, "Define color rule");
1347     }
1348     else
1349     {
1350       focusOnAction.putValue(Action.NAME, "Focus On '" + logger + "'");
1351       hideAction.putValue(Action.NAME, "Ignore '" + logger + "'");
1352       findAction.putValue(Action.NAME, "Find '" + logger + "'");
1353       setRefineFocusAction.putValue(Action.NAME, "Set refine focus field to '" + logger + "'");
1354       updateRefineFocusAction.putValue(Action.NAME, "Add '" + logger + "' to 'refine focus' field");
1355       updateFindAction.putValue(Action.NAME, "Add '" + logger + "' to 'find' field");
1356       defineColorRuleForLoggerAction.putValue(Action.NAME, "Define color rule for '" + logger + "'");
1357     }
1358 
1359     // need to ensure the button doens't update itself with the text, looks stupid otherwise
1360     hideSubLoggersAction.putValue(Action.NAME, "Ignore loggers below selection");
1361     focusOnLoggerButton.setText(null);
1362     ignoreLoggerButton.setText(null);
1363   }
1364 
1365   /**
1366     * Configures varoius listeners etc for the components within
1367     * this Class.
1368     */
1369   private void setupListeners()
1370   {
1371     logTree.addMouseMotionListener(new MouseKeyIconListener());
1372 
1373     /**
1374        * Enable the actions depending on state of the tree selection
1375        */
1376     logTree.addTreeSelectionListener(new TreeSelectionListener()
1377       {
1378         public void valueChanged(TreeSelectionEvent e)
1379         {
1380           TreePath path = e.getNewLeadSelectionPath();
1381           TreeNode node = null;
1382 
1383           if (path != null)
1384           {
1385             node = (TreeNode) path.getLastPathComponent();
1386           }
1387           boolean focusOnSelected = isFocusOnSelected();
1388           //          editLoggerAction.setEnabled(path != null);
1389           currentlySelectedLoggerName = getCurrentlySelectedLoggerName();
1390           focusOnAction.setEnabled(
1391             (path != null) && (node != null) && (node.getParent() != null)
1392             && !hiddenSet.contains(currentlySelectedLoggerName));
1393           hideAction.setEnabled(
1394             (path != null) && (node != null) && (node.getParent() != null));
1395           if (!focusOnAction.isEnabled())
1396           {
1397             setFocusOnSelected(false);
1398           }
1399           else
1400           {
1401           }
1402 
1403           expandAction.setEnabled(path != null);
1404           findAction.setEnabled(path != null);
1405           clearFindNextAction.setEnabled(true);
1406           defineColorRuleForLoggerAction.setEnabled(path != null);
1407           setRefineFocusAction.setEnabled(path != null);
1408           updateRefineFocusAction.setEnabled(path != null);
1409           updateFindAction.setEnabled(path != null);
1410           clearRefineFocusAction.setEnabled(true);
1411 
1412           if (currentlySelectedLoggerName != null)
1413           {
1414             boolean isHidden = hiddenSet.contains(currentlySelectedLoggerName);
1415             popupMenu.hideCheck.setSelected(isHidden);
1416             ignoreLoggerButton.setSelected(isHidden);
1417           }
1418 
1419           collapseAction.setEnabled(path != null);
1420 
1421           reconfigureMenuText();
1422           if (isFocusOnSelected()) {
1423               fireChangeEvent();
1424           }
1425           //fire change event if we toggled focus off
1426           if (focusOnSelected && !isFocusOnSelected()) {
1427               fireChangeEvent();
1428           }
1429           //trigger a table repaint
1430           logPanel.repaint();
1431         }
1432       });
1433 
1434     logTree.addMouseListener(popupListener);
1435 
1436     /**
1437      * This listener ensures the Tool bar toggle button and popup menu check box
1438      * stay in sync, plus notifies all the ChangeListeners that
1439      * an effective filter criteria has been modified
1440      */
1441     focusOnAction.addPropertyChangeListener(new PropertyChangeListener()
1442       {
1443         public void propertyChange(PropertyChangeEvent evt)
1444         {
1445           popupMenu.focusOnCheck.setSelected(isFocusOnSelected());
1446           focusOnLoggerButton.setSelected(isFocusOnSelected());
1447 
1448           if (logTree.getSelectionPath() != null)
1449           {
1450             logTreeModel.nodeChanged(
1451               (TreeNode) logTree.getSelectionPath().getLastPathComponent());
1452           }
1453         }
1454       });
1455 
1456     hideAction.addPropertyChangeListener(new PropertyChangeListener()
1457       {
1458         public void propertyChange(PropertyChangeEvent evt)
1459         {
1460           if (logTree.getSelectionPath() != null)
1461           {
1462             logTreeModel.nodeChanged(
1463               (TreeNode) logTree.getSelectionPath().getLastPathComponent());
1464           }
1465         }
1466       });
1467 
1468     //    /**
1469     //     * Now add a MouseListener that fires the expansion
1470     //     * action if CTRL + DBL CLICK is done.
1471     //     */
1472     //    logTree.addMouseListener(
1473     //      new MouseAdapter() {
1474     //        public void mouseClicked(MouseEvent e) {
1475     //          if (
1476     //            (e.getClickCount() > 1)
1477     //              && ((e.getModifiers() & InputEvent.CTRL_MASK) > 0)
1478     //              && ((e.getModifiers() & InputEvent.BUTTON1_MASK) > 0)) {
1479     //            expandCurrentlySelectedNode();
1480     //            e.consume();
1481     //          } else if (e.getClickCount() > 1) {
1482     //            super.mouseClicked(e);
1483     //            logger.debug("Ignoring dbl click event " + e);
1484     //          }
1485     //        }
1486     //      });
1487 
1488     logTree.addMouseListener(new MouseFocusOnListener());
1489 
1490     /**
1491      * We listen for when the FocusOn action changes, and then  translate
1492      * that to a RuleChange
1493      */
1494     addChangeListener(new ChangeListener()
1495       {
1496         public void stateChanged(ChangeEvent evt)
1497         {
1498           visibilityRuleDelegate.firePropertyChange("rule", null, null);
1499           updateDisplay();
1500         }
1501       });
1502 
1503     visibilityRuleDelegate.addPropertyChangeListener(new PropertyChangeListener()
1504       {
1505         public void propertyChange(PropertyChangeEvent event)
1506         {
1507           if (event.getPropertyName().equals("hiddenSet")) {
1508             updateDisplay();
1509           }
1510         }
1511       });
1512   }
1513 
1514   private void updateDisplay() {
1515       updateHiddenSetModels();
1516       updateIgnoreSummary();
1517       updateIgnoreExpressionSummary();
1518       updateAlwaysDisplayExpressionSummary();
1519   }
1520   
1521   private void updateHiddenSetModels() {
1522       DefaultListModel model = (DefaultListModel) ignoreList.getModel();
1523       model.clear();
1524       List sortedIgnoreList = new ArrayList(hiddenSet);
1525       Collections.sort(sortedIgnoreList);
1526 
1527       for (Iterator iter = sortedIgnoreList.iterator(); iter.hasNext();)
1528       {
1529         String string = (String) iter.next();
1530         model.addElement(string);
1531       }
1532 
1533 //      ignoreList.setModel(model);
1534 
1535   }
1536   private void updateIgnoreSummary() {
1537       ignoreSummary.setText(ignoreList.getModel().getSize() + " hidden loggers");
1538   }
1539 
1540   private void updateIgnoreExpressionSummary() {
1541     ignoreExpressionSummary.setText(ignoreExpressionRule != null?"Ignore (set)":"Ignore (unset)");
1542   }
1543   
1544   private void updateAlwaysDisplayExpressionSummary() {
1545     alwaysDisplayExpressionSummary.setText(alwaysDisplayExpressionRule != null?"Always displayed (set)":"Always displayed (unset)");
1546   }
1547 
1548   private void toggleFocusOnState()
1549   {
1550     setFocusOnSelected(!isFocusOnSelected());
1551     fireChangeEvent();
1552   }
1553 
1554     public Collection getHiddenSet() {
1555         return Collections.unmodifiableSet(hiddenSet);
1556     }
1557 
1558     public String getHiddenExpression() {
1559         String text = ignoreExpressionEntryField.getText();
1560         if (text == null || text.trim().equals("")) {
1561             return null;
1562         }
1563         return text.trim();
1564     }
1565 
1566     public void setHiddenExpression(String hiddenExpression) {
1567         ignoreExpressionEntryField.setText(hiddenExpression);
1568         updateIgnoreExpression(hiddenExpression);
1569     }
1570 
1571     public String getAlwaysDisplayExpression() {
1572         String text = alwaysDisplayExpressionEntryField.getText();
1573         if (text == null || text.trim().equals("")) {
1574             return null;
1575         }
1576         return text.trim();
1577     }
1578 
1579     public void setAlwaysDisplayExpression(String alwaysDisplayExpression) {
1580         alwaysDisplayExpressionEntryField.setText(alwaysDisplayExpression);
1581         updateAlwaysDisplayExpression(alwaysDisplayExpression);
1582     }
1583 
1584     public void loggerNameAdded(String loggerName)
1585     {
1586         //no-op
1587     }
1588 
1589     public void reset()
1590     {
1591         expandRootLatch = false;
1592         //keep track if focuson was active when we were reset
1593         final String logger = currentlySelectedLoggerName;
1594         final boolean focusOnSelected = isFocusOnSelected();
1595         if (logger == null || !focusOnSelected) {
1596             return;
1597         }
1598 
1599         //loggernameAdded runs on EDT
1600         logTreeModel.loggerNameAdded(logger);
1601         EventQueue.invokeLater(new Runnable() {
1602             public void run() {
1603                 setFocusOn(logger);
1604             }
1605         });
1606     }
1607 
1608     //~ Inner Classes ===========================================================
1609 
1610   /**
1611    * DOCUMENT ME!
1612    *
1613    * @author $author$
1614    * @version $Revision$, $Date$
1615    *
1616    * @author Paul Smith &lt;psmith@apache.org&gt;
1617         *
1618         */
1619   private class LoggerNameTreeCellRenderer extends DefaultTreeCellRenderer
1620   {
1621     //~ Constructors ==========================================================
1622 
1623     //    private JPanel panel = new JPanel();
1624     private LoggerNameTreeCellRenderer()
1625     {
1626       super();
1627 
1628       //      panel.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));
1629       //      panel.add(this);
1630       setLeafIcon(null);
1631       setOpaque(false);
1632     }
1633 
1634     //~ Methods ===============================================================
1635 
1636     /* (non-Javadoc)
1637      * @see javax.swing.tree.TreeCellRenderer#getTreeCellRendererComponent(javax.swing.JTree, java.lang.Object, boolean, boolean, boolean, int, boolean)
1638      */
1639     /**
1640      * DOCUMENT ME!
1641      *
1642      * @param tree DOCUMENT ME!
1643      * @param value DOCUMENT ME!
1644      * @param sel DOCUMENT ME!
1645      * @param expanded DOCUMENT ME!
1646      * @param leaf DOCUMENT ME!
1647      * @param row DOCUMENT ME!
1648      * @param focus DOCUMENT ME!
1649      *
1650      * @return DOCUMENT ME!
1651      */
1652     public Component getTreeCellRendererComponent(
1653       JTree tree, Object value, boolean sel, boolean expanded, boolean leaf,
1654       int row, boolean focus)
1655     {
1656       JLabel component =
1657         (JLabel) super.getTreeCellRendererComponent(
1658           tree, value, sel, expanded, leaf, row, focus);
1659 
1660       Font originalFont = new Font(component.getFont().getName(), component.getFont().getStyle(), component.getFont().getSize());
1661 
1662       int style = Font.PLAIN;
1663 
1664       if (sel && focusOnLoggerButton.isSelected())
1665       {
1666         style = style | Font.BOLD;
1667       }
1668 
1669       String logger =
1670         getLoggerName(
1671           new TreePath(((DefaultMutableTreeNode) value).getPath()));
1672 
1673       if (hiddenSet.contains(logger))
1674       {
1675         //        component.setEnabled(false);
1676         //        component.setIcon(leaf?null:getDefaultOpenIcon());
1677         style = style | Font.ITALIC;
1678 
1679         //        logger.debug("TreeRenderer: '" + logger + "' is in hiddenSet, italicizing");
1680       }
1681       else
1682       {
1683         //          logger.debug("TreeRenderer: '" + logger + "' is NOT in hiddenSet, leaving plain");
1684         //        component.setEnabled(true);
1685       }
1686 
1687       if (originalFont != null)
1688       {
1689         Font font2 = originalFont.deriveFont(style);
1690 
1691         if (font2 != null)
1692         {
1693           component.setFont(font2);
1694         }
1695       }
1696 
1697       return component;
1698     }
1699   }
1700 
1701   private class LoggerTreePopupMenu extends JPopupMenu
1702   {
1703     //~ Instance fields =======================================================
1704 
1705     JCheckBoxMenuItem focusOnCheck = new JCheckBoxMenuItem();
1706     JCheckBoxMenuItem hideCheck = new JCheckBoxMenuItem();
1707 
1708     //~ Constructors ==========================================================
1709 
1710     private LoggerTreePopupMenu()
1711     {
1712       initMenu();
1713     }
1714 
1715     //~ Methods ===============================================================
1716 
1717     /* (non-Javadoc)
1718      * @see javax.swing.JPopupMenu#show(java.awt.Component, int, int)
1719      */
1720     /**
1721      * DOCUMENT ME!
1722      *
1723      * @param invoker DOCUMENT ME!
1724      * @param x DOCUMENT ME!
1725      * @param y DOCUMENT ME!
1726      */
1727     public void show(Component invoker, int x, int y)
1728     {
1729       DefaultMutableTreeNode node =
1730         (DefaultMutableTreeNode) logTree.getLastSelectedPathComponent();
1731 
1732       if (node == null)
1733       {
1734         return;
1735       }
1736 
1737       super.show(invoker, x, y);
1738     }
1739 
1740     /**
1741      * DOCUMENT ME!
1742     */
1743     private void initMenu()
1744     {
1745       focusOnCheck.setAction(focusOnAction);
1746       hideCheck.setAction(hideAction);
1747       add(expandAction);
1748       add(collapseAction);
1749       addSeparator();
1750       add(focusOnCheck);
1751       add(hideCheck);
1752       addSeparator();
1753       add(setRefineFocusAction);
1754       add(updateRefineFocusAction);
1755       add(clearRefineFocusAction);
1756       addSeparator();
1757       add(findAction);
1758       add(updateFindAction);
1759       add(clearFindNextAction);
1760 
1761       addSeparator();
1762       add(defineColorRuleForLoggerAction);
1763       addSeparator();
1764 
1765       add(hideSubLoggersAction);
1766       add(clearIgnoreListAction);
1767     }
1768   }
1769 
1770   private final class MouseFocusOnListener extends MouseAdapter
1771   {
1772     //~ Methods ===============================================================
1773 
1774     /* (non-Javadoc)
1775      * @see java.awt.event.MouseListener#mouseClicked(java.awt.event.MouseEvent)
1776      */
1777     /**
1778      * DOCUMENT ME!
1779      *
1780      * @param e DOCUMENT ME!
1781      */
1782     public void mouseClicked(MouseEvent e)
1783     {
1784       if (
1785         (e.getClickCount() > 1)
1786           && ((e.getModifiers() & InputEvent.CTRL_MASK) > 0)
1787           && ((e.getModifiers() & InputEvent.SHIFT_MASK) > 0))
1788       {
1789         ignoreLoggerAtPoint(e.getPoint());
1790         e.consume();
1791         fireChangeEvent();
1792       }
1793       else if (
1794         (e.getClickCount() > 1)
1795           && ((e.getModifiers() & InputEvent.CTRL_MASK) > 0))
1796       {
1797         focusAnLoggerAtPoint(e.getPoint());
1798         e.consume();
1799         fireChangeEvent();
1800       }
1801     }
1802 
1803     /**
1804      * DOCUMENT ME!
1805      *
1806      * @param point
1807      */
1808     private void focusAnLoggerAtPoint(Point point)
1809     {
1810       String logger = getLoggerAtPoint(point);
1811 
1812       if (logger != null)
1813       {
1814         toggleFocusOnState();
1815       }
1816     }
1817 
1818     /**
1819      * DOCUMENT ME!
1820      *
1821      * @param point
1822      * @return
1823      */
1824     private String getLoggerAtPoint(Point point)
1825     {
1826       TreePath path = logTree.getPathForLocation(point.x, point.y);
1827 
1828       if (path != null)
1829       {
1830         return getLoggerName(path);
1831       }
1832 
1833       return null;
1834     }
1835 
1836     /**
1837      * DOCUMENT ME!
1838      *
1839      * @param point
1840      */
1841     private void ignoreLoggerAtPoint(Point point)
1842     {
1843       String logger = getLoggerAtPoint(point);
1844 
1845       if (logger != null)
1846       {
1847         toggleHiddenLogger(logger);
1848         fireChangeEvent();
1849       }
1850     }
1851   }
1852 
1853   private final class MouseKeyIconListener extends MouseMotionAdapter
1854     implements MouseMotionListener
1855   {
1856     //~ Instance fields =======================================================
1857 
1858     Cursor focusOnCursor =
1859       Toolkit.getDefaultToolkit().createCustomCursor(
1860         ChainsawIcons.FOCUS_ON_ICON.getImage(), new Point(10, 10), "");
1861     Cursor ignoreCursor =
1862       Toolkit.getDefaultToolkit().createCustomCursor(
1863         ChainsawIcons.IGNORE_ICON.getImage(), new Point(10, 10), "");
1864 
1865     //~ Methods ===============================================================
1866 
1867     /* (non-Javadoc)
1868      * @see java.awt.event.MouseMotionListener#mouseMoved(java.awt.event.MouseEvent)
1869      */
1870     /**
1871      * DOCUMENT ME!
1872      *
1873      * @param e DOCUMENT ME!
1874      */
1875     public void mouseMoved(MouseEvent e)
1876     {
1877       //      logger.debug(e.toString());
1878       if (
1879         ((e.getModifiers() & InputEvent.CTRL_MASK) > 0)
1880           && ((e.getModifiers() & InputEvent.SHIFT_MASK) > 0))
1881       {
1882         logTree.setCursor(ignoreCursor);
1883       }
1884       else if ((e.getModifiers() & InputEvent.CTRL_MASK) > 0)
1885       {
1886         logTree.setCursor(focusOnCursor);
1887       }
1888       else
1889       {
1890         logTree.setCursor(Cursor.getDefaultCursor());
1891       }
1892     }
1893   }
1894 
1895   class VisibilityRuleDelegate extends AbstractRule {
1896     	public boolean evaluate(LoggingEvent e, Map matches)
1897         {
1898           String currentlySelectedLoggerName = getCurrentlySelectedLoggerName();
1899           boolean hiddenLogger = e.getLoggerName() != null && isHiddenLogger(e.getLoggerName());
1900           boolean hiddenExpression = (ignoreExpressionRule != null && ignoreExpressionRule.evaluate(e, null));
1901           boolean alwaysDisplayExpression = (alwaysDisplayExpressionRule != null && alwaysDisplayExpressionRule.evaluate(e, null));
1902           boolean hidden = (!alwaysDisplayExpression) && (hiddenLogger || hiddenExpression);
1903           if (currentlySelectedLoggerName == null) {
1904           	//if there is no selected logger, pass if not hidden
1905           	return !hidden;
1906           }
1907           boolean result = (e.getLoggerName() != null) && !hidden;
1908 
1909           if (result && isFocusOnSelected())
1910           {
1911             result = (e.getLoggerName() != null && (e.getLoggerName().startsWith(currentlySelectedLoggerName+".") || e.getLoggerName().endsWith(currentlySelectedLoggerName)));
1912           }
1913 
1914           return result;
1915         }
1916 
1917         public void firePropertyChange(String propertyName, Object oldVal, Object newVal)
1918         {
1919             super.firePropertyChange(propertyName, oldVal, newVal);
1920         }
1921     }
1922 
1923 }