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.Component;
23  import java.awt.Cursor;
24  import java.awt.FlowLayout;
25  import java.awt.Font;
26  import java.awt.Point;
27  import java.awt.Toolkit;
28  import java.awt.event.ActionEvent;
29  import java.awt.event.InputEvent;
30  import java.awt.event.MouseAdapter;
31  import java.awt.event.MouseEvent;
32  import java.awt.event.MouseMotionAdapter;
33  import java.awt.event.MouseMotionListener;
34  import java.beans.PropertyChangeEvent;
35  import java.beans.PropertyChangeListener;
36  import java.util.ArrayList;
37  import java.util.Collection;
38  import java.util.Collections;
39  import java.util.Enumeration;
40  import java.util.HashSet;
41  import java.util.Iterator;
42  import java.util.List;
43  import java.util.Set;
44  
45  import javax.swing.AbstractAction;
46  import javax.swing.Action;
47  import javax.swing.BorderFactory;
48  import javax.swing.Box;
49  import javax.swing.BoxLayout;
50  import javax.swing.DefaultListModel;
51  import javax.swing.ImageIcon;
52  import javax.swing.JButton;
53  import javax.swing.JCheckBoxMenuItem;
54  import javax.swing.JDialog;
55  import javax.swing.JLabel;
56  import javax.swing.JList;
57  import javax.swing.JPanel;
58  import javax.swing.JPopupMenu;
59  import javax.swing.JScrollPane;
60  import javax.swing.JToolBar;
61  import javax.swing.JTree;
62  import javax.swing.SwingUtilities;
63  import javax.swing.ToolTipManager;
64  import javax.swing.event.ChangeEvent;
65  import javax.swing.event.ChangeListener;
66  import javax.swing.event.EventListenerList;
67  import javax.swing.event.TreeModelEvent;
68  import javax.swing.event.TreeModelListener;
69  import javax.swing.event.TreeSelectionEvent;
70  import javax.swing.event.TreeSelectionListener;
71  import javax.swing.tree.DefaultMutableTreeNode;
72  import javax.swing.tree.DefaultTreeCellRenderer;
73  import javax.swing.tree.DefaultTreeSelectionModel;
74  import javax.swing.tree.TreeNode;
75  import javax.swing.tree.TreePath;
76  import javax.swing.tree.TreeSelectionModel;
77  
78  import org.apache.log4j.LogManager;
79  import org.apache.log4j.Logger;
80  import org.apache.log4j.chainsaw.icons.ChainsawIcons;
81  import org.apache.log4j.chainsaw.icons.LineIconFactory;
82  import org.apache.log4j.rule.AbstractRule;
83  import org.apache.log4j.rule.Rule;
84  import org.apache.log4j.spi.LoggingEvent;
85  
86  
87  /***
88   * A panel that encapsulates the Logger Name tree, with associated actions
89   * and implements the Rule interface so that it can filter in/out events
90   * that do not match the users request for refining the view based on Loggers.
91   *
92   * @author Paul Smith <psmith@apache.org>
93   */
94  final class LoggerNameTreePanel extends JPanel implements Rule
95  {
96    //~ Static fields/initializers ==============================================
97  
98    private static final int WARN_DEPTH = 4;
99  
100   //~ Instance fields =========================================================
101 
102   private LoggerNameTreeCellRenderer cellRenderer =
103     new LoggerNameTreeCellRenderer();
104   private final Action clearIgnoreListAction;
105   private final Action closeAction;
106   private final JButton closeButton = new SmallButton();
107   private final Action collapseAction;
108   private final JButton collapseButton = new SmallButton();
109   private final Action editLoggerAction;
110   private final JButton editLoggerButton = new SmallButton();
111   private final Action expandAction;
112   private final JButton expandButton = new SmallButton();
113   private final Action focusOnAction;
114   private final SmallToggleButton focusOnLoggerButton =
115     new SmallToggleButton();
116   private final Set hiddenSet = new HashSet();
117   private final Action hideAction;
118   private final LogPanelPreferenceModel preferenceModel;
119 
120   private final JList ignoreList = new JList();
121   private final JScrollPane ignoreListScroll = new JScrollPane(ignoreList);
122   private final JDialog ignoreDialog = new JDialog();
123   private final JLabel ignoreSummary = new JLabel("0 hidden loggers");
124   private final SmallToggleButton ignoreLoggerButton = new SmallToggleButton();
125   private final EventListenerList listenerList = new EventListenerList();
126   private final JTree logTree;
127   private final Logger logger = LogManager.getLogger(LoggerNameTreePanel.class);
128 
129   //  private final EventListenerList focusOnActionListeners =
130   //    new EventListenerList();
131   private final LogPanelLoggerTreeModel logTreeModel;
132   private final PopupListener popupListener;
133   private final LoggerTreePopupMenu popupMenu;
134   private final Rule ruleDelegate;
135   private Rule colorRuleDelegate; 
136   private final JScrollPane scrollTree;
137   private final JToolBar toolbar = new JToolBar();
138 
139   //~ Constructors ============================================================
140 
141   /***
142    * Creates a new LoggerNameTreePanel object.
143    *
144    * @param logTreeModel
145    */
146   LoggerNameTreePanel(LogPanelLoggerTreeModel logTreeModel, LogPanelPreferenceModel preferenceModel)
147   {
148     super();
149     this.logTreeModel = logTreeModel;
150     this.preferenceModel = preferenceModel;
151 
152     setLayout(new BorderLayout());
153 
154     ruleDelegate = new AbstractRule() {
155     	public boolean evaluate(LoggingEvent e)
156         {
157           String currentlySelectedLoggerName = getCurrentlySelectedLoggerName();
158           if (currentlySelectedLoggerName == null) {
159           	//if there is no selected logger, all events should pass
160           	return true;
161           }
162           boolean isHidden = getHiddenSet().contains(e.getLoggerName());
163           boolean result = (e.getLoggerName() != null) && (!isHidden);
164 
165           if (result && isFocusOnSelected())
166           {
167             result = result &&  (e.getLoggerName() != null && (e.getLoggerName().startsWith(currentlySelectedLoggerName+".") || e.getLoggerName().endsWith(currentlySelectedLoggerName))) ;
168           }
169 
170           return result;
171         }
172       };
173     
174     colorRuleDelegate = 
175         new AbstractRule()
176         {
177           public boolean evaluate(LoggingEvent e)
178           {
179             boolean isHidden = getHiddenSet().contains(e.getLoggerName());
180             String currentlySelectedLoggerName = getCurrentlySelectedLoggerName();
181 
182             if (!isFocusOnSelected() && !isHidden && currentlySelectedLoggerName != null && !"".equals(currentlySelectedLoggerName))
183             {
184             	return (e.getLoggerName().startsWith(currentlySelectedLoggerName+".") || e.getLoggerName().endsWith(currentlySelectedLoggerName)) ;
185             }
186             return false;
187           }
188         };
189 
190     logTree =
191     new JTree(logTreeModel)
192       {
193         public String getToolTipText(MouseEvent ev)
194         {
195           if (ev == null)
196           {
197             return null;
198           }
199 
200           TreePath path = logTree.getPathForLocation(ev.getX(), ev.getY());
201 
202           String loggerName = getLoggerName(path);
203 
204           if (hiddenSet.contains(loggerName))
205           {
206             loggerName += " (you are ignoring this logger)";
207           }
208 
209           return loggerName;
210         }
211       };
212 
213     ToolTipManager.sharedInstance().registerComponent(logTree);
214     logTree.setCellRenderer(cellRenderer);
215 
216     //	============================================
217     logTreeModel.addTreeModelListener(new TreeModelListener()
218       {
219         private boolean latched = false;
220 
221         public void treeNodesChanged(TreeModelEvent e)
222         {
223         }
224 
225         public void treeNodesInserted(TreeModelEvent e)
226         {
227           if (!latched)
228           {
229             ensureRootExpanded();
230             latched = true;
231           }
232         }
233 
234         public void treeNodesRemoved(TreeModelEvent e)
235         {
236         }
237 
238         public void treeStructureChanged(TreeModelEvent e)
239         {
240         }
241       });
242 
243     logTree.setEditable(false);
244 
245     //	TODO decide if Multi-selection is useful, and how it would work	
246     TreeSelectionModel selectionModel = new DefaultTreeSelectionModel();
247     selectionModel.setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
248     logTree.setSelectionModel(selectionModel);
249 
250     logTree.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
251     scrollTree = new JScrollPane(logTree);
252     toolbar.setLayout(new BoxLayout(toolbar, BoxLayout.X_AXIS));
253 
254     expandAction = createExpandAction();
255     editLoggerAction = createEditLoggerAction();
256     closeAction = createCloseAction();
257     collapseAction = createCollapseAction();
258     focusOnAction = createFocusOnAction();
259     hideAction = createIgnoreAction();
260     clearIgnoreListAction = createClearIgnoreListAction();
261 
262     popupMenu = new LoggerTreePopupMenu();
263     popupListener = new PopupListener(popupMenu);
264 
265     setupListeners();
266     configureToolbarPanel();
267 
268     add(toolbar, BorderLayout.NORTH);
269     add(scrollTree, BorderLayout.CENTER);
270 
271     ignoreDialog.setTitle("Hidden/Ignored Loggers");
272     ignoreDialog.setModal(true);
273     JPanel ignoreSummaryPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
274     ignoreSummaryPanel.add(ignoreSummary);
275     
276     Action showIgnoreDialogAction = new AbstractAction("...") {
277 
278         public void actionPerformed(ActionEvent e)
279         {
280             ignoreDialog.setVisible(true);
281         }};
282     showIgnoreDialogAction.putValue(Action.SHORT_DESCRIPTION, "Click to view and manage your hidden/ignored loggers");
283     JButton btnShowIgnoreDialog = new SmallButton(showIgnoreDialogAction);
284     
285     ignoreSummaryPanel.add(btnShowIgnoreDialog);
286     add(ignoreSummaryPanel, BorderLayout.SOUTH);
287 
288     ignoreList.setModel(new DefaultListModel());
289     ignoreList.addMouseListener(new MouseAdapter()
290       {
291         public void mouseClicked(MouseEvent e)
292         {
293           if (
294             (e.getClickCount() > 1)
295               && ((e.getModifiers() & InputEvent.BUTTON1_MASK) > 0))
296           {
297             int index = ignoreList.locationToIndex(e.getPoint());
298 
299             if (index >= 0)
300             {
301               String string =
302                 ignoreList.getModel().getElementAt(index).toString();
303               toggleHiddenLogger(string);
304               fireChangeEvent();
305 
306               /***
307                * TODO this needs to get the node that has this logger and fire a visual update
308                */
309               LoggerNameTreePanel.this.logTreeModel.nodeStructureChanged(
310                 (TreeNode) LoggerNameTreePanel.this.logTreeModel.getRoot());
311             }
312           }
313         }
314       });
315     
316     JPanel ignoreListPanel = new JPanel(new BorderLayout());
317     ignoreListScroll.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(),"Double click an entry to unhide it"));
318     ignoreListPanel.add(ignoreListScroll, BorderLayout.CENTER);
319     
320     Box ignoreListButtonPanel = Box.createHorizontalBox();
321     
322     JButton unhideAll = new JButton(new AbstractAction("Unhide All") {
323 
324         public void actionPerformed(final ActionEvent e)
325         {
326              SwingUtilities.invokeLater(new Runnable() {
327 
328                 public void run()
329                 {
330                     clearIgnoreListAction.actionPerformed(e);
331                 }}); 
332             
333         }});
334     ignoreListButtonPanel.add(unhideAll);
335     
336     ignoreListButtonPanel.add(Box.createHorizontalGlue());
337     JButton ignoreCloseButton = new JButton(new AbstractAction("Close") {
338 
339         public void actionPerformed(ActionEvent e)
340         {
341             ignoreDialog.setVisible(false);
342             
343         }});
344     ignoreListButtonPanel.add(ignoreCloseButton);
345     
346     
347     ignoreListPanel.add(ignoreListButtonPanel, BorderLayout.SOUTH);
348     
349     
350     ignoreDialog.getContentPane().add(ignoreListPanel);
351     ignoreDialog.pack();
352   }
353 
354   //~ Methods =================================================================
355 
356   /***
357    * Adds a change Listener to this LoggerNameTreePanel to be notfied
358    * when the State of the Focus or Hidden details have changed.
359    *
360    * @param l
361    */
362   public void addChangeListener(ChangeListener l)
363   {
364     listenerList.add(ChangeListener.class, l);
365   }
366 
367   /* (non-Javadoc)
368    * @see org.apache.log4j.chainsaw.rule.Rule#evaluate(org.apache.log4j.spi.LoggingEvent)
369    */
370   /***
371    * DOCUMENT ME!
372    *
373    * @param e DOCUMENT ME!
374    *
375    * @return DOCUMENT ME!
376    */
377   public boolean evaluate(LoggingEvent e)
378   {
379     return ruleDelegate.evaluate(e);
380   }
381   
382   public Rule getLoggerColorRule() {
383   	return colorRuleDelegate;
384   }
385 
386   /***
387    * DOCUMENT ME!
388    *
389    * @param l DOCUMENT ME!
390    */
391   public void removeChangeListener(ChangeListener l)
392   {
393     listenerList.remove(ChangeListener.class, l);
394   }
395 
396   /***
397    * Ensures the Focus is set to a specific logger name
398    * @param logger
399    */
400   public void setFocusOn(String newLogger)
401   {
402     DefaultMutableTreeNode node = logTreeModel.lookupLogger(newLogger);
403 
404     if (node != null)
405     {
406       TreeNode[] nodes = node.getPath();
407       TreePath treePath = new TreePath(nodes);
408       logTree.setSelectionPath(treePath);
409 
410       if (!focusOnLoggerButton.isSelected())
411       {
412         focusOnLoggerButton.doClick();
413       }
414     }
415     else
416     {
417       logger.error("failed to lookup logger " + newLogger);
418     }
419   }
420 
421   /***
422    * DOCUMENT ME!
423    *
424    * @param logger
425    */
426   protected void toggleHiddenLogger(String logger)
427   {
428     if (!hiddenSet.contains(logger))
429     {
430       hiddenSet.add(logger);
431     }
432     else
433     {
434       hiddenSet.remove(logger);
435     }
436 
437     firePropertyChange("hiddenSet", (Object) null, (Object) null);
438   }
439 
440   /***
441    * Returns the full name of the Logger that is represented by
442    * the currently selected Logger node in the tree.
443    *
444    * This is the dotted name, of the current node including all it's parents.
445    *
446    * If multiple Nodes are selected, the first path is used
447    * @return Logger Name or null if nothing selected
448    */
449   String getCurrentlySelectedLoggerName()
450   {
451     TreePath[] paths = logTree.getSelectionPaths();
452 
453     if ((paths == null) || (paths.length == 0))
454     {
455       return null;
456     }
457 
458     TreePath firstPath = paths[0];
459 
460     return getLoggerName(firstPath);
461   }
462 
463   /***
464    * Returns an unmodifiable set of those Loggers marked as hidden.
465    * @return
466    */
467   Set getHiddenSet()
468   {
469     return Collections.unmodifiableSet(hiddenSet);
470   }
471 
472   /***
473    * Returns the full
474    * @param path DOCUMENT ME!
475    * @return
476    */
477   String getLoggerName(TreePath path)
478   {
479     if (path != null)
480     {
481       Object[] objects = path.getPath();
482       StringBuffer buf = new StringBuffer();
483 
484       for (int i = 1; i < objects.length; i++)
485       {
486         buf.append(objects[i].toString());
487 
488         if (i < (objects.length - 1))
489         {
490           buf.append(".");
491         }
492       }
493 
494       return buf.toString();
495     }
496 
497     return null;
498   }
499 
500   /***
501    * adds a Collection of Strings to the ignore List and notifise all listeners of
502    * both the "hiddenSet" property and those expecting the Rule to change
503    * via the ChangeListener interface 
504    * @param fqnLoggersToIgnore
505    */
506   void ignore(Collection fqnLoggersToIgnore)
507   {
508     hiddenSet.addAll(fqnLoggersToIgnore);
509     firePropertyChange("hiddenSet", null, null);
510     fireChangeEvent();
511   }
512 
513   /***
514    * Returns true if the FocusOn element has been selected
515    * @return true if the FocusOn action/lement has been selected
516    */
517   boolean isFocusOnSelected()
518   {
519     return focusOnAction.getValue("checked") != null;
520   }
521 
522   void setFocusOnSelected(boolean selected)
523   {
524     if (selected)
525     {
526       focusOnAction.putValue("checked", Boolean.TRUE);
527     }
528     else
529     {
530       focusOnAction.putValue("checked", null);
531     }
532   }
533 
534   /***
535    * Given the currently selected nodes
536    * collapses all the children of those nodes.
537    *
538    */
539   private void collapseCurrentlySelectedNode()
540   {
541     TreePath[] paths = logTree.getSelectionPaths();
542 
543     if (paths == null)
544     {
545       return;
546     }
547 
548       logger.debug("Collapsing all children of selected node");
549 
550     for (int i = 0; i < paths.length; i++)
551     {
552       TreePath path = paths[i];
553       DefaultMutableTreeNode node =
554         (DefaultMutableTreeNode) path.getLastPathComponent();
555       Enumeration enumeration = node.depthFirstEnumeration();
556 
557       while (enumeration.hasMoreElements())
558       {
559         DefaultMutableTreeNode child =
560           (DefaultMutableTreeNode) enumeration.nextElement();
561 
562         if ((child.getParent() != null) && (child != node))
563         {
564           TreeNode[] nodes =
565             ((DefaultMutableTreeNode) child.getParent()).getPath();
566 
567           TreePath treePath = new TreePath(nodes);
568           logTree.collapsePath(treePath);
569         }
570       }
571     }
572 
573     ensureRootExpanded();
574   }
575 
576   /***
577      * configures all the components that are used in the mini-toolbar of this
578      * component
579      */
580   private void configureToolbarPanel()
581   {
582     toolbar.setFloatable(false);
583 
584     expandButton.setAction(expandAction);
585     expandButton.setText(null);
586     collapseButton.setAction(collapseAction);
587     collapseButton.setText(null);
588     focusOnLoggerButton.setAction(focusOnAction);
589     focusOnLoggerButton.setText(null);
590     ignoreLoggerButton.setAction(hideAction);
591     ignoreLoggerButton.setText(null);
592 
593     expandButton.setFont(expandButton.getFont().deriveFont(Font.BOLD));
594     collapseButton.setFont(collapseButton.getFont().deriveFont(Font.BOLD));
595 
596     editLoggerButton.setAction(editLoggerAction);
597     editLoggerButton.setText(null);
598     closeButton.setAction(closeAction);
599     closeButton.setText(null);
600 
601     toolbar.add(expandButton);
602     toolbar.add(collapseButton);
603     toolbar.addSeparator();
604     toolbar.add(focusOnLoggerButton);
605     toolbar.add(ignoreLoggerButton);
606 
607     //    toolbar.add(editLoggerButton);
608     toolbar.addSeparator();
609 
610     toolbar.add(Box.createHorizontalGlue());
611     toolbar.add(closeButton);
612     toolbar.add(Box.createHorizontalStrut(5));
613   }
614 
615   /***
616    * DOCUMENT ME!
617    *
618    * @return
619   */
620   private Action createClearIgnoreListAction()
621   {
622     Action action = new AbstractAction("Clear Ignore list", null)
623       {
624         public void actionPerformed(ActionEvent e)
625         {
626           ignoreLoggerButton.setSelected(false);
627           logTreeModel.reload();
628           hiddenSet.clear();
629           fireChangeEvent();
630         }
631       };
632 
633     action.putValue(
634       Action.SHORT_DESCRIPTION,
635       "Removes all entries from the Ignore list so you can see their events in the view");
636 
637     return action;
638   }
639 
640   /***
641      * An action that closes (hides) this panel
642     * @return
643     */
644   private Action createCloseAction()
645   {
646     Action action = new AbstractAction()
647       {
648         public void actionPerformed(ActionEvent e)
649         {
650             preferenceModel.setLogTreePanelVisible(false);
651         }
652       };
653 
654     action.putValue(Action.NAME, "Close");
655     action.putValue(Action.SHORT_DESCRIPTION, "Closes the Logger panel");
656     action.putValue(Action.SMALL_ICON, LineIconFactory.createCloseIcon());
657 
658     return action;
659   }
660 
661   /***
662    * DOCUMENT ME!
663    *
664    * @return
665   */
666   private Action createCollapseAction()
667   {
668     Action action = new AbstractAction()
669       {
670         public void actionPerformed(ActionEvent e)
671         {
672           collapseCurrentlySelectedNode();
673         }
674       };
675 
676     action.putValue(Action.SMALL_ICON, LineIconFactory.createCollapseIcon());
677     action.putValue(Action.NAME, "Collapse Branch");
678     action.putValue(
679       Action.SHORT_DESCRIPTION,
680       "Collapses all the children of the currently selected node");
681     action.setEnabled(false);
682 
683     return action;
684   }
685 
686   private Action createEditLoggerAction()
687   {
688     Action action = new AbstractAction()
689       {
690         public void actionPerformed(ActionEvent e)
691         {
692           // TODO Auto-generated method stub
693         }
694       };
695 
696     //    TODO enable this when it's ready.
697     action.putValue("enabled", Boolean.FALSE);
698 
699     action.putValue(Action.NAME, "Edit filters/colors");
700     action.putValue(
701       Action.SHORT_DESCRIPTION,
702       "Allows you to specify filters and coloring for this Logger");
703     action.putValue(
704       Action.SMALL_ICON, new ImageIcon(ChainsawIcons.ICON_EDIT_RECEIVER));
705     action.setEnabled(false);
706 
707     return action;
708   }
709 
710   /***
711    * Creates an action that is used to expand the selected node
712    * and all children
713    * @return an Action
714    */
715   private Action createExpandAction()
716   {
717     Action action = new AbstractAction()
718       {
719         public void actionPerformed(ActionEvent e)
720         {
721           expandCurrentlySelectedNode();
722         }
723       };
724 
725     action.putValue(Action.SMALL_ICON, LineIconFactory.createExpandIcon());
726     action.putValue(Action.NAME, "Expand branch");
727     action.putValue(
728       Action.SHORT_DESCRIPTION,
729       "Expands all the child nodes of the currently selected node, recursively");
730     action.setEnabled(false);
731 
732     return action;
733   }
734 
735   /***
736    * DOCUMENT ME!
737    *
738    * @return
739   */
740   private Action createFocusOnAction()
741   {
742     final Action action = new AbstractAction()
743       {
744         public void actionPerformed(ActionEvent e)
745         {
746           toggleFocusOnState();
747         }
748       };
749 
750     action.putValue(Action.NAME, "Focus");
751     action.putValue(
752       Action.SHORT_DESCRIPTION,
753       "Allows you to Focus on the selected logger by setting a filter that discards all but this Logger");
754     action.putValue(
755       Action.SMALL_ICON, new ImageIcon(ChainsawIcons.WINDOW_ICON));
756 
757     action.setEnabled(false);
758 
759     return action;
760   }
761 
762   /***
763    * DOCUMENT ME!
764    *
765    * @return
766     */
767   private Action createIgnoreAction()
768   {
769     Action action =
770       new AbstractAction(
771         "Ignore this Logger", new ImageIcon(ChainsawIcons.ICON_COLLAPSE))
772       {
773         public void actionPerformed(ActionEvent e)
774         {
775           String logger = getCurrentlySelectedLoggerName();
776 
777           if (logger != null)
778           {
779             toggleHiddenLogger(logger);
780             logTreeModel.nodeChanged(
781               (TreeNode) logTree.getSelectionPath().getLastPathComponent());
782             ignoreLoggerButton.setSelected(hiddenSet.contains(logger));
783             focusOnAction.setEnabled(!hiddenSet.contains(logger));
784             popupMenu.hideCheck.setSelected(hiddenSet.contains(logger));
785           }
786 
787           fireChangeEvent();
788         }
789       };
790 
791     action.putValue(
792       Action.SHORT_DESCRIPTION,
793       "Adds the selected Logger to your Ignore list, filtering those events from view");
794 
795     return action;
796   }
797 
798   private void ensureRootExpanded()
799   {
800       logger.debug("Ensuring Root node is expanded.");
801 
802     final DefaultMutableTreeNode root =
803       (DefaultMutableTreeNode) logTreeModel.getRoot();
804     SwingUtilities.invokeLater(new Runnable()
805       {
806         public void run()
807         {
808           logTree.expandPath(new TreePath(root));
809         }
810       });
811   }
812 
813   /***
814    * Expands the currently selected node (if any)
815    * including all the children.
816    *
817    */
818   private void expandCurrentlySelectedNode()
819   {
820     TreePath[] paths = logTree.getSelectionPaths();
821 
822     if (paths == null)
823     {
824       return;
825     }
826 
827       logger.debug("Expanding all children of selected node");
828 
829     for (int i = 0; i < paths.length; i++)
830     {
831       TreePath path = paths[i];
832 
833       /***
834        * TODO this is commented out, right now it expands all nodes including the root, so if there is a large tree..... look out.
835        */
836 
837       //      /***
838       //       * Handle an expansion of the Root node by only doing the first level.
839       //       * Safe...
840       //       */
841       //      if (path.getPathCount() == 1) {
842       //        logTree.expandPath(path);
843       //
844       //        return;
845       //      }
846 
847       DefaultMutableTreeNode treeNode =
848         (DefaultMutableTreeNode) path.getLastPathComponent();
849 
850       Enumeration depthEnum = treeNode.depthFirstEnumeration();
851 
852       if (!depthEnum.hasMoreElements())
853       {
854         break;
855       }
856 
857       List depths = new ArrayList();
858 
859       while (depthEnum.hasMoreElements())
860       {
861         depths.add(
862           new Integer(
863             ((DefaultMutableTreeNode) depthEnum.nextElement()).getDepth()));
864       }
865 
866       Collections.sort(depths);
867       Collections.reverse(depths);
868 
869       int maxDepth = ((Integer) depths.get(0)).intValue();
870 
871       if (maxDepth > WARN_DEPTH)
872       {
873         logger.warn("Should warn user, depth=" + maxDepth);
874       }
875 
876       depthEnum = treeNode.depthFirstEnumeration();
877 
878       while (depthEnum.hasMoreElements())
879       {
880         DefaultMutableTreeNode node =
881           (DefaultMutableTreeNode) depthEnum.nextElement();
882 
883         if (node.isLeaf())
884         {
885           TreeNode[] nodes =
886             ((DefaultMutableTreeNode) node.getParent()).getPath();
887           TreePath treePath = new TreePath(nodes);
888 
889           logger.debug("Expanding path:" + treePath);
890 
891           logTree.expandPath(treePath);
892         }
893       }
894     }
895   }
896 
897   private void fireChangeEvent()
898   {
899     ChangeListener[] listeners =
900       (ChangeListener[]) listenerList.getListeners(ChangeListener.class);
901     ChangeEvent e = null;
902 
903     for (int i = 0; i < listeners.length; i++)
904     {
905       if (e == null)
906       {
907         e = new ChangeEvent(this);
908       }
909 
910       listeners[i].stateChanged(e);
911     }
912   }
913 
914   private void reconfigureMenuText()
915   {
916     String logger = getCurrentlySelectedLoggerName();
917 
918     if ((logger == null) || (logger.length() == 0))
919     {
920       focusOnAction.putValue(Action.NAME, "Focus On...");
921       hideAction.putValue(Action.NAME, "Ignore ...");
922     }
923     else
924     {
925       focusOnAction.putValue(Action.NAME, "Focus On '" + logger + "'");
926       hideAction.putValue(Action.NAME, "Ignore '" + logger + "'");
927     }
928 
929     // need to ensure the button doens't update itself with the text, looks stupid otherwise
930     focusOnLoggerButton.setText(null);
931     ignoreLoggerButton.setText(null);
932   }
933 
934   /***
935     * Configures varoius listeners etc for the components within
936     * this Class.
937     */
938   private void setupListeners()
939   {
940     logTree.addMouseMotionListener(new MouseKeyIconListener());
941 
942     /***
943        * Enable the actions depending on state of the tree selection
944        */
945     logTree.addTreeSelectionListener(new TreeSelectionListener()
946       {
947         public void valueChanged(TreeSelectionEvent e)
948         {
949           TreePath path = e.getNewLeadSelectionPath();
950           TreeNode node = null;
951 
952           if (path != null)
953           {
954             node = (TreeNode) path.getLastPathComponent();
955           }
956 
957           //          editLoggerAction.setEnabled(path != null);
958           String logger = getCurrentlySelectedLoggerName();
959           focusOnAction.setEnabled(
960             (path != null) && (node != null) && (node.getParent() != null)
961             && !hiddenSet.contains(logger));
962           hideAction.setEnabled(
963             (path != null) && (node != null) && (node.getParent() != null)
964             && !isFocusOnSelected());
965 
966           if (!focusOnAction.isEnabled())
967           {
968             setFocusOnSelected(false);
969           }
970           else
971           {
972           }
973 
974           expandAction.setEnabled(path != null);
975 
976           if (logger != null)
977           {
978             boolean isHidden = hiddenSet.contains(logger);
979             popupMenu.hideCheck.setSelected(isHidden);
980             ignoreLoggerButton.setSelected(isHidden);
981           }
982 
983           collapseAction.setEnabled(path != null);
984 
985           reconfigureMenuText();
986         }
987       });
988 
989     logTree.addMouseListener(popupListener);
990 
991     /***
992      * This listener ensures the Tool bar toggle button and popup menu check box
993      * stay in sync, plus notifies all the ChangeListeners that
994      * an effective filter criteria has been modified
995      */
996     focusOnAction.addPropertyChangeListener(new PropertyChangeListener()
997       {
998         public void propertyChange(PropertyChangeEvent evt)
999         {
1000           popupMenu.focusOnCheck.setSelected(isFocusOnSelected());
1001           focusOnLoggerButton.setSelected(isFocusOnSelected());
1002 
1003           if (logTree.getSelectionPath() != null)
1004           {
1005             logTreeModel.nodeChanged(
1006               (TreeNode) logTree.getSelectionPath().getLastPathComponent());
1007           }
1008 
1009           fireChangeEvent();
1010         }
1011       });
1012 
1013     hideAction.addPropertyChangeListener(new PropertyChangeListener()
1014       {
1015         public void propertyChange(PropertyChangeEvent evt)
1016         {
1017           if (logTree.getSelectionPath() != null)
1018           {
1019             logTreeModel.nodeChanged(
1020               (TreeNode) logTree.getSelectionPath().getLastPathComponent());
1021           }
1022 
1023           fireChangeEvent();
1024         }
1025       });
1026 
1027     //    /***
1028     //     * Now add a MouseListener that fires the expansion
1029     //     * action if CTRL + DBL CLICK is done.
1030     //     */
1031     //    logTree.addMouseListener(
1032     //      new MouseAdapter() {
1033     //        public void mouseClicked(MouseEvent e) {
1034     //          if (
1035     //            (e.getClickCount() > 1)
1036     //              && ((e.getModifiers() & InputEvent.CTRL_MASK) > 0)
1037     //              && ((e.getModifiers() & InputEvent.BUTTON1_MASK) > 0)) {
1038     //            expandCurrentlySelectedNode();
1039     //            e.consume();
1040     //          } else if (e.getClickCount() > 1) {
1041     //            super.mouseClicked(e);
1042     //            logger.debug("Ignoring dbl click event " + e);
1043     //          }
1044     //        }
1045     //      });
1046 
1047     logTree.addMouseListener(new MouseFocusOnListener());
1048 
1049     /***
1050      * We listen for when the FocusOn action changes, and then  translate
1051      * that to a RuleChange
1052      */
1053     addChangeListener(new ChangeListener()
1054       {
1055         public void stateChanged(ChangeEvent evt)
1056         {
1057           firePropertyChange("rule", null, null);
1058           updateAllIgnoreStuff();
1059         }
1060       });
1061 
1062     addPropertyChangeListener("hiddenSet", new PropertyChangeListener()
1063       {
1064         public void propertyChange(PropertyChangeEvent arg0)
1065         {
1066           updateAllIgnoreStuff();
1067         }
1068       });
1069   }
1070 
1071   private void updateAllIgnoreStuff() {
1072       updateHiddenSetModels();
1073       updateIgnoreSummary();
1074   }
1075   
1076   private void updateHiddenSetModels() {
1077       DefaultListModel model = (DefaultListModel) ignoreList.getModel();
1078       model.clear();
1079       List sortedIgnoreList = new ArrayList(getHiddenSet());
1080       Collections.sort(sortedIgnoreList);
1081 
1082       for (Iterator iter = sortedIgnoreList.iterator(); iter.hasNext();)
1083       {
1084         String string = (String) iter.next();
1085         model.addElement(string);
1086       }
1087 
1088 //      ignoreList.setModel(model);
1089 
1090   }
1091   private void updateIgnoreSummary() {
1092       ignoreSummary.setText(ignoreList.getModel().getSize() + " hidden loggers");
1093   }
1094   
1095   private void toggleFocusOnState()
1096   {
1097     setFocusOnSelected(!isFocusOnSelected());
1098     hideAction.setEnabled(!isFocusOnSelected());
1099   }
1100 
1101   //~ Inner Classes ===========================================================
1102 
1103   /***
1104    * DOCUMENT ME!
1105    *
1106    * @author $author$
1107    * @version $Revision: 529672 $, $Date: 2007-04-17 11:59:53 -0500 (Tue, 17 Apr 2007) $
1108    *
1109    * @author Paul Smith <psmith@apache.org>
1110         *
1111         */
1112   private class LoggerNameTreeCellRenderer extends DefaultTreeCellRenderer
1113   {
1114     //~ Constructors ==========================================================
1115 
1116     //    private JPanel panel = new JPanel();
1117     private LoggerNameTreeCellRenderer()
1118     {
1119       super();
1120 
1121       //      panel.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));
1122       //      panel.add(this);
1123       setLeafIcon(null);
1124       setOpaque(false);
1125     }
1126 
1127     //~ Methods ===============================================================
1128 
1129     /* (non-Javadoc)
1130      * @see javax.swing.tree.TreeCellRenderer#getTreeCellRendererComponent(javax.swing.JTree, java.lang.Object, boolean, boolean, boolean, int, boolean)
1131      */
1132     /***
1133      * DOCUMENT ME!
1134      *
1135      * @param tree DOCUMENT ME!
1136      * @param value DOCUMENT ME!
1137      * @param sel DOCUMENT ME!
1138      * @param expanded DOCUMENT ME!
1139      * @param leaf DOCUMENT ME!
1140      * @param row DOCUMENT ME!
1141      * @param focus DOCUMENT ME!
1142      *
1143      * @return DOCUMENT ME!
1144      */
1145     public Component getTreeCellRendererComponent(
1146       JTree tree, Object value, boolean sel, boolean expanded, boolean leaf,
1147       int row, boolean focus)
1148     {
1149       JLabel component =
1150         (JLabel) super.getTreeCellRendererComponent(
1151           tree, value, sel, expanded, leaf, row, focus);
1152 
1153       Font originalFont = component.getFont();
1154 
1155       int style = Font.PLAIN;
1156 
1157       if (sel && focusOnLoggerButton.isSelected())
1158       {
1159         style = style | Font.BOLD;
1160       }
1161 
1162       String logger =
1163         getLoggerName(
1164           new TreePath(((DefaultMutableTreeNode) value).getPath()));
1165 
1166       if (hiddenSet.contains(logger))
1167       {
1168         //        component.setEnabled(false);
1169         //        component.setIcon(leaf?null:getDefaultOpenIcon());
1170         style = style | Font.ITALIC;
1171 
1172         //        logger.debug("TreeRenderer: '" + logger + "' is in hiddenSet, italicizing");
1173       }
1174       else
1175       {
1176         //          logger.debug("TreeRenderer: '" + logger + "' is NOT in hiddenSet, leaving plain");
1177         //        component.setEnabled(true);
1178       }
1179 
1180       if (originalFont != null)
1181       {
1182         Font font2 = originalFont.deriveFont(style);
1183 
1184         if (font2 != null)
1185         {
1186           component.setFont(font2);
1187         }
1188       }
1189 
1190       return component;
1191     }
1192   }
1193 
1194   private class LoggerTreePopupMenu extends JPopupMenu
1195   {
1196     //~ Instance fields =======================================================
1197 
1198     JCheckBoxMenuItem focusOnCheck = new JCheckBoxMenuItem();
1199     JCheckBoxMenuItem hideCheck = new JCheckBoxMenuItem();
1200 
1201     //~ Constructors ==========================================================
1202 
1203     private LoggerTreePopupMenu()
1204     {
1205       initMenu();
1206     }
1207 
1208     //~ Methods ===============================================================
1209 
1210     /* (non-Javadoc)
1211      * @see javax.swing.JPopupMenu#show(java.awt.Component, int, int)
1212      */
1213     /***
1214      * DOCUMENT ME!
1215      *
1216      * @param invoker DOCUMENT ME!
1217      * @param x DOCUMENT ME!
1218      * @param y DOCUMENT ME!
1219      */
1220     public void show(Component invoker, int x, int y)
1221     {
1222       DefaultMutableTreeNode node =
1223         (DefaultMutableTreeNode) logTree.getLastSelectedPathComponent();
1224 
1225       if (node == null)
1226       {
1227         return;
1228       }
1229 
1230       super.show(invoker, x, y);
1231     }
1232 
1233     /***
1234      * DOCUMENT ME!
1235     */
1236     private void initMenu()
1237     {
1238       add(expandAction);
1239       add(collapseAction);
1240       addSeparator();
1241       focusOnCheck.setAction(focusOnAction);
1242       hideCheck.setAction(hideAction);
1243       add(focusOnCheck);
1244       add(hideCheck);
1245 
1246       //      add(editLoggerAction);
1247       addSeparator();
1248       add(clearIgnoreListAction);
1249     }
1250   }
1251 
1252   private final class MouseFocusOnListener extends MouseAdapter
1253   {
1254     //~ Methods ===============================================================
1255 
1256     /* (non-Javadoc)
1257      * @see java.awt.event.MouseListener#mouseClicked(java.awt.event.MouseEvent)
1258      */
1259     /***
1260      * DOCUMENT ME!
1261      *
1262      * @param e DOCUMENT ME!
1263      */
1264     public void mouseClicked(MouseEvent e)
1265     {
1266       if (
1267         (e.getClickCount() > 1)
1268           && ((e.getModifiers() & InputEvent.CTRL_MASK) > 0)
1269           && ((e.getModifiers() & InputEvent.SHIFT_MASK) > 0))
1270       {
1271         ignoreLoggerAtPoint(e.getPoint());
1272         e.consume();
1273         fireChangeEvent();
1274       }
1275       else if (
1276         (e.getClickCount() > 1)
1277           && ((e.getModifiers() & InputEvent.CTRL_MASK) > 0))
1278       {
1279         focusAnLoggerAtPoint(e.getPoint());
1280         e.consume();
1281         fireChangeEvent();
1282       }
1283     }
1284 
1285     /***
1286      * DOCUMENT ME!
1287      *
1288      * @param point
1289      */
1290     private void focusAnLoggerAtPoint(Point point)
1291     {
1292       String logger = getLoggerAtPoint(point);
1293 
1294       if (logger != null)
1295       {
1296         toggleFocusOnState();
1297       }
1298     }
1299 
1300     /***
1301      * DOCUMENT ME!
1302      *
1303      * @param point
1304      * @return
1305      */
1306     private String getLoggerAtPoint(Point point)
1307     {
1308       TreePath path = logTree.getPathForLocation(point.x, point.y);
1309 
1310       if (path != null)
1311       {
1312         return getLoggerName(path);
1313       }
1314 
1315       return null;
1316     }
1317 
1318     /***
1319      * DOCUMENT ME!
1320      *
1321      * @param point
1322      */
1323     private void ignoreLoggerAtPoint(Point point)
1324     {
1325       String logger = getLoggerAtPoint(point);
1326 
1327       if (logger != null)
1328       {
1329         toggleHiddenLogger(logger);
1330       }
1331     }
1332   }
1333 
1334   private final class MouseKeyIconListener extends MouseMotionAdapter
1335     implements MouseMotionListener
1336   {
1337     //~ Instance fields =======================================================
1338 
1339     Cursor focusOnCursor =
1340       Toolkit.getDefaultToolkit().createCustomCursor(
1341         ChainsawIcons.FOCUS_ON_ICON.getImage(), new Point(10, 10), "");
1342     Cursor ignoreCursor =
1343       Toolkit.getDefaultToolkit().createCustomCursor(
1344         ChainsawIcons.IGNORE_ICON.getImage(), new Point(10, 10), "");
1345 
1346     //~ Methods ===============================================================
1347 
1348     /* (non-Javadoc)
1349      * @see java.awt.event.MouseMotionListener#mouseMoved(java.awt.event.MouseEvent)
1350      */
1351     /***
1352      * DOCUMENT ME!
1353      *
1354      * @param e DOCUMENT ME!
1355      */
1356     public void mouseMoved(MouseEvent e)
1357     {
1358       //      logger.debug(e.toString());
1359       if (
1360         ((e.getModifiers() & InputEvent.CTRL_MASK) > 0)
1361           && ((e.getModifiers() & InputEvent.SHIFT_MASK) > 0))
1362       {
1363         logTree.setCursor(ignoreCursor);
1364       }
1365       else if ((e.getModifiers() & InputEvent.CTRL_MASK) > 0)
1366       {
1367         logTree.setCursor(focusOnCursor);
1368       }
1369       else
1370       {
1371         logTree.setCursor(Cursor.getDefaultCursor());
1372       }
1373     }
1374   }
1375 }