You are viewing a plain text version of this content. The canonical link for it is here.
Posted to general@logging.apache.org by sd...@apache.org on 2010/03/23 19:13:28 UTC

svn commit: r926692 - in /logging/chainsaw/trunk/src/main: java/org/apache/log4j/chainsaw/LogPanel.java java/org/apache/log4j/chainsaw/zeroconf/ZeroConfPlugin.java resources/org/apache/log4j/chainsaw/help/release-notes.html

Author: sdeboy
Date: Tue Mar 23 18:13:27 2010
New Revision: 926692

URL: http://svn.apache.org/viewvc?rev=926692&view=rev
Log:
New feature: Chainsaw 'refine focus' field now supports auto-filtering
 - entering text in the refine focus field now causes the popup to display matching entries

Modified:
    logging/chainsaw/trunk/src/main/java/org/apache/log4j/chainsaw/LogPanel.java
    logging/chainsaw/trunk/src/main/java/org/apache/log4j/chainsaw/zeroconf/ZeroConfPlugin.java
    logging/chainsaw/trunk/src/main/resources/org/apache/log4j/chainsaw/help/release-notes.html

Modified: logging/chainsaw/trunk/src/main/java/org/apache/log4j/chainsaw/LogPanel.java
URL: http://svn.apache.org/viewvc/logging/chainsaw/trunk/src/main/java/org/apache/log4j/chainsaw/LogPanel.java?rev=926692&r1=926691&r2=926692&view=diff
==============================================================================
--- logging/chainsaw/trunk/src/main/java/org/apache/log4j/chainsaw/LogPanel.java (original)
+++ logging/chainsaw/trunk/src/main/java/org/apache/log4j/chainsaw/LogPanel.java Tue Mar 23 18:13:27 2010
@@ -59,6 +59,7 @@ import java.text.DateFormat;
 import java.text.NumberFormat;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Date;
 import java.util.Enumeration;
 import java.util.EventObject;
@@ -72,11 +73,13 @@ import java.util.StringTokenizer;
 import java.util.Vector;
 
 import javax.swing.AbstractAction;
+import javax.swing.AbstractListModel;
 import javax.swing.Action;
 import javax.swing.BorderFactory;
 import javax.swing.Box;
 import javax.swing.BoxLayout;
 import javax.swing.ButtonGroup;
+import javax.swing.ComboBoxEditor;
 import javax.swing.ImageIcon;
 import javax.swing.JButton;
 import javax.swing.JCheckBoxMenuItem;
@@ -99,6 +102,7 @@ import javax.swing.JTextField;
 import javax.swing.JToolBar;
 import javax.swing.KeyStroke;
 import javax.swing.ListSelectionModel;
+import javax.swing.MutableComboBoxModel;
 import javax.swing.SwingConstants;
 import javax.swing.SwingUtilities;
 import javax.swing.UIManager;
@@ -109,6 +113,8 @@ import javax.swing.event.DocumentEvent;
 import javax.swing.event.DocumentListener;
 import javax.swing.event.ListSelectionEvent;
 import javax.swing.event.ListSelectionListener;
+import javax.swing.event.PopupMenuEvent;
+import javax.swing.event.PopupMenuListener;
 import javax.swing.event.TableColumnModelEvent;
 import javax.swing.event.TableColumnModelListener;
 import javax.swing.event.TableModelEvent;
@@ -118,9 +124,6 @@ import javax.swing.table.TableColumn;
 import javax.swing.table.TableColumnModel;
 import javax.swing.text.Document;
 
-import com.thoughtworks.xstream.XStream;
-import com.thoughtworks.xstream.io.xml.DomDriver;
-
 import org.apache.log4j.LogManager;
 import org.apache.log4j.Logger;
 import org.apache.log4j.PatternLayout;
@@ -145,6 +148,9 @@ import org.apache.log4j.rule.Rule;
 import org.apache.log4j.spi.LoggingEvent;
 import org.apache.log4j.spi.LoggingEventFieldResolver;
 
+import com.thoughtworks.xstream.XStream;
+import com.thoughtworks.xstream.io.xml.DomDriver;
+
 
 /**
  * A LogPanel provides a view to a collection of LoggingEvents.<br>
@@ -245,9 +251,9 @@ public class LogPanel extends DockablePa
   private int previousLastIndex = -1;
   private final DateFormat timestampExpressionFormat = new SimpleDateFormat(Constants.TIMESTAMP_RULE_FORMAT);
   private final Logger logger = LogManager.getLogger(LogPanel.class);
-  private final Vector filterExpressionVector;
   private static final Color INVALID_EXPRESSION_BACKGROUND = new Color(251, 186, 186);
   private TableCellEditor markerCellEditor;
+  private AutoFilterComboBox filterCombo;
 
     /**
    * Creates a new LogPanel object.  If a LogPanel with this identifier has
@@ -953,10 +959,8 @@ public class LogPanel extends DockablePa
 
     upperLeftPanel.add(filterLabel);
 
-    //hold a reference to the combobox model so that we can check to prevent duplicates
-    //final Vector filterExpressionVector = new Vector();
-    filterExpressionVector = new Vector();
     //add (hopefully useful) default filters
+    Vector filterExpressionVector = new Vector();
     filterExpressionVector.add("LEVEL == TRACE");
     filterExpressionVector.add("LEVEL >= DEBUG");
     filterExpressionVector.add("LEVEL >= INFO");
@@ -964,20 +968,14 @@ public class LogPanel extends DockablePa
     filterExpressionVector.add("LEVEL >= ERROR");
     filterExpressionVector.add("LEVEL == FATAL");
     
-    final JComboBox filterCombo = new JComboBox(filterExpressionVector);
+    filterCombo = new AutoFilterComboBox(filterExpressionVector);
     filterCombo.setSelectedIndex(-1);
-    final JTextField filterText;
+    final JTextField filterText =(JTextField) filterCombo.getEditor().getEditorComponent();
+    filterText.getDocument().addDocumentListener(new DelayedFilterTextDocumentListener(filterText));
+    filterText.setToolTipText("Enter an expression, press enter to add to list");
+    filterText.addKeyListener(new ExpressionRuleContext(filterModel, filterText));
 
     if (filterCombo.getEditor().getEditorComponent() instanceof JTextField) {
-      String comboToolTipText =
-        "Enter an expression, press enter to add to list";
-      filterText = (JTextField) filterCombo.getEditor().getEditorComponent();
-      filterText.setToolTipText(comboToolTipText);
-      filterText.addKeyListener(
-        new ExpressionRuleContext(filterModel, filterText));
-      filterText.getDocument().addDocumentListener(
-        new DelayedFilterTextDocumentListener(filterText));
-      filterCombo.setEditable(true);
       filterCombo.addActionListener(
         new AbstractAction() {
           public void actionPerformed(ActionEvent e) {
@@ -993,25 +991,14 @@ public class LogPanel extends DockablePa
                   filterText.setBackground(INVALID_EXPRESSION_BACKGROUND);
                 return;
               }
-
-              //should be 'valid expression' check
-              if (!(filterExpressionVector.contains(filterCombo.getSelectedItem()))) {
-                filterCombo.addItem(filterCombo.getSelectedItem());
-              }
+            //add to the combo box
+            filterCombo.addItem(filterCombo.getSelectedItem());
             }
           }
         });
+    }
       upperPanel.add(filterCombo, BorderLayout.CENTER);
       filterLabel.setLabelFor(filterCombo);
-    } else {
-      filterText = new JTextField();
-      filterText.setToolTipText("Enter an expression");
-      filterText.addKeyListener(
-        new ExpressionRuleContext(filterModel, filterText));
-      filterText.getDocument().addDocumentListener(
-        new DelayedFilterTextDocumentListener(filterText));
-      upperPanel.add(filterText, BorderLayout.CENTER);
-    }
 
     upperPanel.add(upperLeftPanel, BorderLayout.WEST);
 
@@ -1026,13 +1013,12 @@ public class LogPanel extends DockablePa
                 public void actionPerformed(ActionEvent e){
                 	Object selectedItem = filterCombo.getSelectedItem();
                     if (e.getSource() == clearButton && selectedItem != null && !selectedItem.toString().equals("")){
-                        if (filterExpressionVector.contains(selectedItem.toString())){
-                            filterExpressionVector.remove(selectedItem.toString());
-                        }
+                        //don't just remove the entry from the store, clear the refine focus field
+                        filterText.setText(null);
+                        int index = filterCombo.getSelectedIndex();
                         filterCombo.setSelectedIndex(-1);
+                        filterCombo.removeItemAt(index);
                     }
-                    //don't just remove the entry from the store, clear the refine focus field
-                    filterText.setText(null);
                 }
             }
     );
@@ -1645,9 +1631,7 @@ public class LogPanel extends DockablePa
                 savedVector = (Vector) in.readObject();
                 for(int i = 0 ; i < savedVector.size() ; i++){
                     Object item = savedVector.get(i);
-                    if(!filterExpressionVector.contains(item)){
-                        filterExpressionVector.add(item);
-                    }
+                    filterCombo.addItem(item);
                 }
                 if (versionNumber > 1) {
                     //update prefModel columns to include defaults
@@ -1753,7 +1737,8 @@ public class LogPanel extends DockablePa
     	s.writeObject(undockedFrame.getSize());
         //this is a version number written to the file to identify that there is a Vector serialized after this
         s.writeInt(LOG_PANEL_SERIALIZATION_VERSION_NUMBER);
-        s.writeObject(filterExpressionVector);
+        //don't write filterexpressionvector, write the combobox's model's backing vector
+        s.writeObject(filterCombo.getModelData());
     } catch (Exception ex) {
         ex.printStackTrace();
         // TODO need to log this..
@@ -3007,4 +2992,222 @@ public class LogPanel extends DockablePa
             return textField;
         }
     }
+
+    static class AutoFilterComboBox extends JComboBox {
+        private boolean bypassFiltering;
+        private List allEntries = new ArrayList();
+        private List displayedEntries = new ArrayList();
+        private AutoFilterComboBoxModel model = new AutoFilterComboBoxModel();
+        //editor component
+        private final JTextField textField = new JTextField();
+
+        public AutoFilterComboBox(Collection entries) {
+            if (entries != null) {
+                for (Iterator iter=entries.iterator();iter.hasNext();) {
+                    Object nextObject = iter.next();
+                    model.addElement(nextObject);
+                }
+            }
+            setModel(model);
+            setEditor(new AutoFilterEditor());
+            ((JTextField)getEditor().getEditorComponent()).getDocument().addDocumentListener(new AutoFilterDocumentListener());
+            setEditable(true);
+            addPopupMenuListener(new PopupMenuListenerImpl());
+        }
+
+        public Vector getModelData() {
+            return new Vector(allEntries);
+        }
+
+        private void refilter() {
+            //only refilter if we're not bypassing filtering
+            if (bypassFiltering) {
+                return;
+            }
+            displayedEntries.clear();
+            bypassFiltering = true;
+            String textToMatch = getEditor().getItem().toString();
+                model.removeAllElements();
+                List entriesCopy = new ArrayList(allEntries);
+                for (Iterator iter = entriesCopy.iterator();iter.hasNext();) {
+                    String thisEntry = iter.next().toString();
+                    if (thisEntry.toLowerCase().contains(textToMatch.toLowerCase())) {
+                        displayedEntries.add(thisEntry);
+                        model.addElement(thisEntry);
+                    }
+                }
+                bypassFiltering = false;
+                //TODO: on no-match, don't filter at all (show the popup?)
+                if (displayedEntries.size() > 0 && !textToMatch.equals("")) {
+                    showPopup();
+                } else {
+                    hidePopup();
+                }
+        }
+
+        class AutoFilterEditor implements ComboBoxEditor {
+            public Component getEditorComponent() {
+                return textField;
+            }
+
+            public void setItem(Object item) {
+                if (bypassFiltering) {
+                    return;
+                }
+                bypassFiltering = true;
+                if (item == null) {
+                    textField.setText("");
+                } else {
+                    textField.setText(item.toString());
+                }
+                bypassFiltering = false;
+            }
+
+            public Object getItem() {
+                return textField.getText();
+            }
+
+            public void selectAll() {
+                textField.selectAll();
+            }
+
+            public void addActionListener(ActionListener listener) {
+                textField.addActionListener(listener);
+            }
+
+            public void removeActionListener(ActionListener listener) {
+                textField.removeActionListener(listener);
+            }
+        }
+
+        class AutoFilterDocumentListener implements DocumentListener {
+            public void insertUpdate(DocumentEvent e) {
+                refilter();
+            }
+
+            public void removeUpdate(DocumentEvent e) {
+                refilter();
+            }
+
+            public void changedUpdate(DocumentEvent e) {
+                refilter();
+            }
+        }
+
+        class AutoFilterComboBoxModel extends AbstractListModel implements MutableComboBoxModel {
+            private Object selectedItem;
+
+            public void addElement(Object obj) {
+                //assuming add is to displayed list...add to full list (only if not a dup)
+                if (allEntries.contains(obj)) {
+                    return;
+                }
+                bypassFiltering = true;
+                allEntries.add(obj);
+                displayedEntries.add(obj);
+                fireIntervalAdded(this, displayedEntries.size() - 1, displayedEntries.size() -1);
+                bypassFiltering = false;
+                refilter();
+            }
+
+            public void removeElement(Object obj) {
+                int index = displayedEntries.indexOf(obj);
+                if (index != -1) {
+                    removeElementAt(index);
+                }
+            }
+
+            public void insertElementAt(Object obj, int index) {
+                //assuming add is to displayed list...add to full list (only if not a dup)
+                if (allEntries.contains(obj)) {
+                    return;
+                }
+                bypassFiltering = true;
+                displayedEntries.add(index, obj);
+                allEntries.add(obj);
+                fireIntervalAdded(this, index, index);
+                bypassFiltering = false;
+                refilter();
+            }
+
+            public void removeElementAt(int index) {
+                bypassFiltering = true;
+                //assuming removal is from displayed list..remove from full list
+                Object obj = displayedEntries.get(index);
+                allEntries.remove(obj);
+                fireContentsChanged(this, 0, displayedEntries.size());
+                bypassFiltering = false;
+                refilter();
+            }
+
+            public void setSelectedItem(Object item) {
+                if ((selectedItem != null && !selectedItem.equals(item)) || selectedItem == null && item != null) {
+                    selectedItem = item;
+                    fireContentsChanged(this, -1, -1);
+                }
+            }
+
+            public Object getSelectedItem() {
+                return selectedItem;
+            }
+
+            public int getSize() {
+                return displayedEntries.size();
+            }
+
+            public Object getElementAt(int index) {
+                if (index >= 0 && index < displayedEntries.size()) {
+                    return displayedEntries.get(index);
+                }
+                return null;
+            }
+
+
+            public void removeAllElements() {
+                bypassFiltering = true;
+                displayedEntries.clear();
+                fireContentsChanged(this, 0, displayedEntries.size());
+                bypassFiltering = false;
+            }
+
+            public void showAllElements() {
+                bypassFiltering = true;
+                displayedEntries.clear();
+                displayedEntries.addAll(allEntries);
+                fireContentsChanged(this, 0, displayedEntries.size());
+                bypassFiltering = false;
+            }
+        }
+
+        private class PopupMenuListenerImpl implements PopupMenuListener {
+            private boolean willBecomeVisible = false;
+
+            public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
+                if (!willBecomeVisible) {
+                    //we already have a match but we're showing the popup - unfilter
+                    if (displayedEntries.contains(textField.getText())) {
+                        model.showAllElements();
+                    }
+
+                    //workaround for bug http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4743225
+                    //the height of the popup after updating entries in this listener was not updated..
+                    JComboBox list = (JComboBox) e.getSource();
+                    willBecomeVisible = true; // the flag is needed to prevent a loop
+                    try {
+                        list.getUI().setPopupVisible(list, true);
+                    } finally {
+                        willBecomeVisible = false;
+                    }
+                }
+            }
+
+            public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
+                //no-op
+            }
+
+            public void popupMenuCanceled(PopupMenuEvent e) {
+                //no-op
+            }
+        }
+    }
 }

Modified: logging/chainsaw/trunk/src/main/java/org/apache/log4j/chainsaw/zeroconf/ZeroConfPlugin.java
URL: http://svn.apache.org/viewvc/logging/chainsaw/trunk/src/main/java/org/apache/log4j/chainsaw/zeroconf/ZeroConfPlugin.java?rev=926692&r1=926691&r2=926692&view=diff
==============================================================================
--- logging/chainsaw/trunk/src/main/java/org/apache/log4j/chainsaw/zeroconf/ZeroConfPlugin.java (original)
+++ logging/chainsaw/trunk/src/main/java/org/apache/log4j/chainsaw/zeroconf/ZeroConfPlugin.java Tue Mar 23 18:13:27 2010
@@ -111,7 +111,9 @@ public class ZeroConfPlugin extends GUIP
     }
 
     public void shutdown() {
-        jmDNS.close();
+        if (jmDNS != null) {
+            jmDNS.close();
+        }
         save();
     }
 

Modified: logging/chainsaw/trunk/src/main/resources/org/apache/log4j/chainsaw/help/release-notes.html
URL: http://svn.apache.org/viewvc/logging/chainsaw/trunk/src/main/resources/org/apache/log4j/chainsaw/help/release-notes.html?rev=926692&r1=926691&r2=926692&view=diff
==============================================================================
--- logging/chainsaw/trunk/src/main/resources/org/apache/log4j/chainsaw/help/release-notes.html (original)
+++ logging/chainsaw/trunk/src/main/resources/org/apache/log4j/chainsaw/help/release-notes.html Tue Mar 23 18:13:27 2010
@@ -10,6 +10,12 @@
 <b>NOTE:</b> The mechanism and format used to persist settings in Chainsaw is subject to change.  If you are experiencing problems displaying events in Chainsaw, please delete everything in the $user.dir/.chainsaw directory and restart Chainsaw.
 <br>
 <h1>1.99.99</h1>
+<h2>23 Mar 2010</h2>
+<ul>
+<li>
+'Refine focus' field now supports auto-filtering: entering text in the refine focus field now causes the popup to display matching entries (reminder: you can press enter to add entries to this box, and those entries are persisted)
+</li>
+</ul>
 <h2>20 Mar 2010</h2>
 <ul>
 <li>