sdeboy      2003/12/31 01:54:40

  Modified:    src/java/org/apache/log4j/chainsaw
                        ChainsawCyclicBufferTableModel.java LogUI.java
                        LogPanel.java TableColorizingRenderer.java
                        JSortTable.java
                        ApplicationPreferenceModelPanel.java
                        ApplicationPreferenceModel.java
                        ChainsawAppenderHandler.java
               src/java/org/apache/log4j/rule ColorRule.java
                        PartialTextMatchRule.java LevelInequalityRule.java
                        ExpressionRuleContext.java EqualsRule.java
                        NotEqualsRule.java InequalityRule.java
                        ExistsRule.java LikeRule.java
               src/java/org/apache/log4j/chainsaw/prefs default.properties
  Added:       src/java/org/apache/log4j/spi LoggingEventFieldResolver.java
  Removed:     src/java/org/apache/log4j/chainsaw
                        LoggingEventFieldResolver.java
  Log:
  - added tooltip duration application-level preference
  - modified tooltip logic - if tooltips are not enabled and icons are shown for 
levels, the icon's value is displayed.  if tooltips are displayed, the normal tooltip 
is displayed when the mouse is over the level icon
  - updated row selection code to prevent unnecessary 'blinking' of selection
  - updated detailpane update logic to ensure detail pane was staying in sync with 
selection
  - removed unneeded default property, added default tooltip duration (4000 ms) and 
changed default responsiveness to 3
  - moved loggingeventfieldresolver to spi package
  - modified cyclicbuffertablemodel to remove unneeded variables and fix a bug where 
it was always iterating through mdc keyset
  - fixed bug where as new events were received, if scrolltobottom was off and you 
scrolled, the display was always shifting back to the selected row
  - removed unused parameter in logpanel constructor
  
  Revision  Changes    Path
  1.15      +18 -39    
logging-log4j/src/java/org/apache/log4j/chainsaw/ChainsawCyclicBufferTableModel.java
  
  Index: ChainsawCyclicBufferTableModel.java
  ===================================================================
  RCS file: 
/home/cvs/logging-log4j/src/java/org/apache/log4j/chainsaw/ChainsawCyclicBufferTableModel.java,v
  retrieving revision 1.14
  retrieving revision 1.15
  diff -u -r1.14 -r1.15
  --- ChainsawCyclicBufferTableModel.java       14 Dec 2003 20:35:09 -0000      1.14
  +++ ChainsawCyclicBufferTableModel.java       31 Dec 2003 09:54:40 -0000      1.15
  @@ -211,16 +211,14 @@
        */
     public void sort() {
       if (sortEnabled) {
  -      int size = 0;
   
         synchronized (filteredList) {
  -        size = filteredList.size();
           Collections.sort(
             filteredList,
             new ColumnComparator(currentSortColumn, currentSortAscending));
         }
   
  -      fireTableRowsUpdated(0, size);
  +      fireTableDataChanged();
       }
     }
   
  @@ -410,7 +408,6 @@
     public boolean isAddRow(LoggingEvent e, boolean valueIsAdjusting) {
       boolean rowAdded = false;
   
  -    int newRow = 0;
       Object id = e.getProperty(ChainsawConstants.LOG4J_ID_KEY);
   
       if (id == null) {
  @@ -424,20 +421,13 @@
       }
       idSet.add(id);
       unfilteredList.add(e);
  -
       rowAdded = true;
   
       if ((displayRule == null) || (displayRule.evaluate(e))) {
         synchronized (filteredList) {
           filteredList.add(e);
  -        newRow = filteredList.size() - 1;
  +        fireTableRowsInserted(filteredList.size(),filteredList.size());
         }
  -
  -      rowAdded = true;
  -    }
  -
  -    if (!valueIsAdjusting) {
  -      notifyCountListeners();
       }
   
       /**
  @@ -445,32 +435,21 @@
        */
       boolean newColumn = uniqueMDCKeys.addAll(e.getMDCKeySet());
   
  -    /**
  -     * If so, we should add them as columns and notify listeners.
  -     */
  -    for (Iterator iter = e.getMDCKeySet().iterator(); iter.hasNext();) {
  -      Object key = iter.next();
  -
  -      if (!columnNames.contains(key)) {
  -        columnNames.add(key);
  -        LogLog.debug("Adding col '" + key + "', columNames=" + columnNames);
  -        fireNewKeyColumnAdded(
  -          new NewKeyEvent(
  -            this, columnNames.indexOf(key), key, e.getMDC(key.toString())));
  -      }
  -    }
  -
  -    if (!isCyclic() && !newColumn) {
  -      fireTableRowsInserted(newRow, newRow);
  -    } else {
  -      if (
  -        newColumn
  -          || (unfilteredList.size() == ((CyclicBufferList) unfilteredList)
  -          .getMaxSize())) {
  -        fireTableDataChanged();
  -      } else {
  -        fireTableRowsInserted(newRow, newRow);
  -      }
  +    if (newColumn) {
  +        /**
  +         * If so, we should add them as columns and notify listeners.
  +         */
  +        for (Iterator iter = e.getMDCKeySet().iterator(); iter.hasNext();) {
  +          Object key = iter.next();
  +    
  +          if (!columnNames.contains(key)) {
  +            columnNames.add(key);
  +            LogLog.debug("Adding col '" + key + "', columNames=" + columnNames);
  +            fireNewKeyColumnAdded(
  +              new NewKeyEvent(
  +                this, columnNames.indexOf(key), key, e.getMDC(key.toString())));
  +          }
  +        }
       }
   
       return rowAdded;
  @@ -705,7 +684,7 @@
                   }
   
                   monitor.setNote("Refiltering...");
  -                filterExecutor.run();
  +                reFilter();
                   monitor.setProgress(index++);
                 } finally {
                   monitor.close();
  
  
  
  1.68      +11 -3     logging-log4j/src/java/org/apache/log4j/chainsaw/LogUI.java
  
  Index: LogUI.java
  ===================================================================
  RCS file: /home/cvs/logging-log4j/src/java/org/apache/log4j/chainsaw/LogUI.java,v
  retrieving revision 1.67
  retrieving revision 1.68
  diff -u -r1.67 -r1.68
  --- LogUI.java        31 Dec 2003 07:05:33 -0000      1.67
  +++ LogUI.java        31 Dec 2003 09:54:40 -0000      1.68
  @@ -99,6 +99,7 @@
   import javax.swing.KeyStroke;
   import javax.swing.SwingConstants;
   import javax.swing.SwingUtilities;
  +import javax.swing.ToolTipManager;
   import javax.swing.UIManager;
   import javax.swing.event.ChangeEvent;
   import javax.swing.event.ChangeListener;
  @@ -985,6 +986,15 @@
                handler.setIdentifierExpression(evt.getNewValue().toString());
                }
        } );
  +
  +    applicationPreferenceModel.addPropertyChangeListener("toolTipDisplayMillis", 
new PropertyChangeListener() {
  +        public void propertyChange(PropertyChangeEvent evt) {
  +             
ToolTipManager.sharedInstance().setDismissDelay(((Integer)evt.getNewValue()).intValue());
  +        }
  +    } );
  +    
ToolTipManager.sharedInstance().setDismissDelay(applicationPreferenceModel.getToolTipDisplayMillis());
  +    
  +
       applicationPreferenceModel.addPropertyChangeListener("responsiveness", new 
PropertyChangeListener() {
         public void propertyChange(PropertyChangeEvent evt) {
           int value = ((Integer)evt.getNewValue()).intValue();
  @@ -1655,11 +1665,9 @@
         }
   
         if (!getPanelMap().containsKey(ident)) {
  -        final String eventType =
  -          ((ChainsawEventBatchEntry) eventBatchEntrys.get(0)).getEventType();
   
           final LogPanel thisPanel =
  -          new LogPanel(getStatusBar(), ident, eventType);
  +          new LogPanel(getStatusBar(), ident);
   
           thisPanel.addEventCountListener(new TabIconHandler(ident));
   
  
  
  
  1.44      +67 -86    logging-log4j/src/java/org/apache/log4j/chainsaw/LogPanel.java
  
  Index: LogPanel.java
  ===================================================================
  RCS file: /home/cvs/logging-log4j/src/java/org/apache/log4j/chainsaw/LogPanel.java,v
  retrieving revision 1.43
  retrieving revision 1.44
  diff -u -r1.43 -r1.44
  --- LogPanel.java     20 Dec 2003 00:22:06 -0000      1.43
  +++ LogPanel.java     31 Dec 2003 09:54:40 -0000      1.44
  @@ -54,29 +54,6 @@
    */
   package org.apache.log4j.chainsaw;
   
  -import org.apache.log4j.Layout;
  -import org.apache.log4j.PatternLayout;
  -import org.apache.log4j.chainsaw.color.ColorPanel;
  -import org.apache.log4j.chainsaw.color.RuleColorizer;
  -import org.apache.log4j.chainsaw.filter.FilterModel;
  -import org.apache.log4j.chainsaw.icons.ChainsawIcons;
  -import org.apache.log4j.chainsaw.icons.LineIconFactory;
  -import org.apache.log4j.chainsaw.layout.DefaultLayoutFactory;
  -import org.apache.log4j.chainsaw.layout.EventDetailLayout;
  -import org.apache.log4j.chainsaw.layout.LayoutEditorPane;
  -import org.apache.log4j.chainsaw.messages.MessageCenter;
  -import org.apache.log4j.chainsaw.prefs.LoadSettingsEvent;
  -import org.apache.log4j.chainsaw.prefs.Profileable;
  -import org.apache.log4j.chainsaw.prefs.SaveSettingsEvent;
  -import org.apache.log4j.chainsaw.prefs.SettingsManager;
  -import org.apache.log4j.helpers.ISO8601DateFormat;
  -import org.apache.log4j.helpers.LogLog;
  -import org.apache.log4j.rule.AbstractRule;
  -import org.apache.log4j.rule.ExpressionRule;
  -import org.apache.log4j.rule.ExpressionRuleContext;
  -import org.apache.log4j.rule.Rule;
  -import org.apache.log4j.spi.LoggingEvent;
  -
   import java.awt.BorderLayout;
   import java.awt.Container;
   import java.awt.Dimension;
  @@ -96,10 +73,8 @@
   import java.awt.event.MouseMotionAdapter;
   import java.awt.event.WindowAdapter;
   import java.awt.event.WindowEvent;
  -
   import java.beans.PropertyChangeEvent;
   import java.beans.PropertyChangeListener;
  -
   import java.io.BufferedInputStream;
   import java.io.BufferedOutputStream;
   import java.io.EOFException;
  @@ -111,10 +86,8 @@
   import java.io.ObjectInputStream;
   import java.io.ObjectOutputStream;
   import java.io.Serializable;
  -
   import java.text.NumberFormat;
   import java.text.SimpleDateFormat;
  -
   import java.util.ArrayList;
   import java.util.Enumeration;
   import java.util.HashMap;
  @@ -163,6 +136,30 @@
   import javax.swing.table.TableColumn;
   import javax.swing.table.TableColumnModel;
   
  +import org.apache.log4j.Layout;
  +import org.apache.log4j.PatternLayout;
  +import org.apache.log4j.chainsaw.color.ColorPanel;
  +import org.apache.log4j.chainsaw.color.RuleColorizer;
  +import org.apache.log4j.chainsaw.filter.FilterModel;
  +import org.apache.log4j.chainsaw.icons.ChainsawIcons;
  +import org.apache.log4j.chainsaw.icons.LineIconFactory;
  +import org.apache.log4j.chainsaw.layout.DefaultLayoutFactory;
  +import org.apache.log4j.chainsaw.layout.EventDetailLayout;
  +import org.apache.log4j.chainsaw.layout.LayoutEditorPane;
  +import org.apache.log4j.chainsaw.messages.MessageCenter;
  +import org.apache.log4j.chainsaw.prefs.LoadSettingsEvent;
  +import org.apache.log4j.chainsaw.prefs.Profileable;
  +import org.apache.log4j.chainsaw.prefs.SaveSettingsEvent;
  +import org.apache.log4j.chainsaw.prefs.SettingsManager;
  +import org.apache.log4j.helpers.ISO8601DateFormat;
  +import org.apache.log4j.helpers.LogLog;
  +import org.apache.log4j.rule.AbstractRule;
  +import org.apache.log4j.rule.ExpressionRule;
  +import org.apache.log4j.rule.ExpressionRuleContext;
  +import org.apache.log4j.rule.Rule;
  +import org.apache.log4j.spi.LoggingEvent;
  +import org.apache.log4j.spi.LoggingEventFieldResolver;
  +
   
   /**
      * LogPanel encapsulates all the necessary bits and pieces of a
  @@ -191,7 +188,6 @@
       private final LogPanelPreferenceModel preferenceModel = new 
LogPanelPreferenceModel();
       private final LogPanelPreferencePanel preferencesPanel = new 
LogPanelPreferencePanel(preferenceModel);
       private final ColorPanel colorPanel;
  -    private String profileName = null;
       private final JDialog detailDialog = new JDialog((JFrame) null, true);
       final JPanel detailPanel = new JPanel(new BorderLayout());
       private final TableColorizingRenderer renderer;
  @@ -213,8 +209,7 @@
       private final JToolBar undockedToolbar;
       private RuleColorizer colorizer = new RuleColorizer();
   
  -    public LogPanel(final ChainsawStatusBar statusBar, final String ident,
  -        String eventType) {
  +    public LogPanel(final ChainsawStatusBar statusBar, final String ident) {
           identifier = ident;
           this.statusBar = statusBar;
   
  @@ -259,6 +254,7 @@
                       table.tableChanged(new TableModelEvent(getModel()));
                   }
               });
  +
           
setDetailPaneConversionPattern(DefaultLayoutFactory.getDefaultPatternLayout());
           ((EventDetailLayout) 
toolTipLayout).setConversionPattern(DefaultLayoutFactory.getDefaultPatternLayout());
   
  @@ -300,6 +296,15 @@
           colorFrame.setIconImage(((ImageIcon) 
ChainsawIcons.ICON_PREFERENCES).getImage());
   
           renderer = new TableColorizingRenderer(colorizer);
  +
  +        preferenceModel.addPropertyChangeListener("toolTips",
  +            new PropertyChangeListener() {
  +                public void propertyChange(PropertyChangeEvent evt) {
  +                    renderer.setToolTipsVisible(((Boolean) 
evt.getNewValue()).booleanValue());
  +                }
  +            });
  +        renderer.setToolTipsVisible(preferenceModel.isToolTips());
  +
           colorPanel = new ColorPanel(colorizer, filterModel);
   
           colorFrame.getContentPane().add(colorPanel);
  @@ -742,7 +747,6 @@
                               (evt.getValueIsAdjusting())) {
                           return;
                       }
  -
                       final ListSelectionModel lsm = (ListSelectionModel) 
evt.getSource();
   
                       if (lsm.isSelectionEmpty()) {
  @@ -1498,6 +1502,7 @@
                   "loggerPrecision"));
           getPreferenceModel().setToolTips(event.asBoolean("toolTips"));
           getPreferenceModel().setScrollToBottom(event.asBoolean("scrollToBottom"));
  +        scrollToBottom.scroll(event.asBoolean("scrollToBottom"));
           getPreferenceModel().setLogTreePanelVisible(event.asBoolean(
                   "logTreePanelVisible"));
           getPreferenceModel().setDetailPaneVisible(event.asBoolean(
  @@ -1700,28 +1705,20 @@
               return;
           }
   
  -        table.getSelectionModel().setValueIsAdjusting(true);
  +        //table.getSelectionModel().setValueIsAdjusting(true);
   
           boolean rowAdded = false;
  -        LoggingEvent lastSelected = null;
  -
  -        if (table.getSelectedRow() > -1) {
  -            lastSelected = tableModel.getRow(table.getSelectedRow());
  -        }
  +        int currentRow = getCurrentRow();
   
           for (Iterator iter = eventBatchEntrys.iterator(); iter.hasNext();) {
               ChainsawEventBatchEntry entry = (ChainsawEventBatchEntry) iter.next();
   
  -            //        Vector v = formatFields(entry.getEventVector());
  -            final String eventType = entry.getEventType();
  -
               updateOtherModels(entry);
   
               boolean isCurrentRowAdded = tableModel.isAddRow(entry.getEvent(),
                       true);
               rowAdded = rowAdded ? true : isCurrentRowAdded;
           }
  -
           table.getSelectionModel().setValueIsAdjusting(false);
   
           //tell the model to notify the count listeners
  @@ -1734,11 +1731,10 @@
                   table.scrollToBottom(table.columnAtPoint(
                           table.getVisibleRect().getLocation()));
               } else {
  -                if (lastSelected != null) {
  -                    table.scrollToRow(tableModel.getRowIndex(lastSelected),
  +                    table.scrollToRow(currentRow,
                           table.columnAtPoint(table.getVisibleRect().getLocation()));
  -                }
  -            }
  +                    detailPaneUpdater.setSelectedRow(currentRow);
  +             }
           }
       }
   
  @@ -1752,7 +1748,6 @@
       private void updateOtherModels(ChainsawEventBatchEntry entry) {
           LoggingEvent event = entry.getEvent();
           String eventType = entry.getEventType();
  -        String level = event.getLevel().toString();
   
           /**
            * EventContainer is a LoggerNameModel imp, use that for notifing
  @@ -2010,7 +2005,6 @@
   
           public void columnAdded(TableColumnModelEvent e) {
               //      LogLog.debug("Detected columnAdded" + e);
  -            TableColumnModel columnModel = (TableColumnModel) e.getSource();
               Enumeration enum = table.getColumnModel().getColumns();
   
               while (enum.hasMoreElements()) {
  @@ -2043,7 +2037,6 @@
        */
       class DetailPaneUpdater implements PropertyChangeListener {
           private int selectedRow = -1;
  -        private int lastRow = -1;
           private final JEditorPane pane;
           private final EventContainer model;
           private final LogPanel panel;
  @@ -2056,19 +2049,11 @@
           }
   
           public void setSelectedRow(int row) {
  -            if (row == -1) {
  -                lastRow = 0;
  -            }
  -
               selectedRow = row;
               updateDetailPane();
           }
   
           private void updateDetailPane() {
  -            updateDetailPane(false);
  -        }
  -
  -        private void updateDetailPane(boolean force) {
               String text = null;
   
               /**
  @@ -2078,39 +2063,35 @@
                   return;
               }
   
  -            if ((selectedRow != lastRow) || force) {
  -                if (selectedRow == -1) {
  -                    text = "Nothing selected";
  -                } else {
  -                    LoggingEvent event = model.getRow(selectedRow);
  -
  -                    if (event != null) {
  -                        Layout layout = panel.getDetailPaneLayout();
  -                        StringBuffer buf = new StringBuffer();
  -                        buf.append(layout.getHeader())
  -                           .append(layout.format(event)).append(layout.getFooter());
  -                        text = buf.toString();
  -                    }
  -                }
  +            if (selectedRow == -1) {
  +                text = "Nothing selected";
  +            } else {
  +                LoggingEvent event = model.getRow(selectedRow);
   
  -                if (!((text != null) && !text.equals(""))) {
  -                    text = "Nothing selected";
  +                if (event != null) {
  +                    Layout layout = panel.getDetailPaneLayout();
  +                    StringBuffer buf = new StringBuffer();
  +                    buf.append(layout.getHeader())
  +                       .append(layout.format(event)).append(layout.getFooter());
  +                    text = buf.toString();
                   }
  +            }
   
  -                lastRow = selectedRow;
  -
  -                final String text2 = text;
  -                SwingUtilities.invokeLater(new Runnable() {
  -                        public void run() {
  -                            pane.setText(text2);
  -                        }
  -                    });
  -                SwingUtilities.invokeLater(new Runnable() {
  -                        public void run() {
  -                            pane.setCaretPosition(0);
  -                        }
  -                    });
  +            if (!((text != null) && !text.equals(""))) {
  +                text = "Nothing selected";
               }
  +
  +            final String text2 = text;
  +            SwingUtilities.invokeLater(new Runnable() {
  +                    public void run() {
  +                        pane.setText(text2);
  +                    }
  +                });
  +            SwingUtilities.invokeLater(new Runnable() {
  +                    public void run() {
  +                        pane.setCaretPosition(0);
  +                    }
  +                });
           }
   
           /* (non-Javadoc)
  @@ -2119,7 +2100,7 @@
           public void propertyChange(PropertyChangeEvent arg0) {
               SwingUtilities.invokeLater(new Runnable() {
                       public void run() {
  -                        updateDetailPane(true);
  +                        updateDetailPane();
                       }
                   });
           }
  
  
  
  1.11      +10 -1     
logging-log4j/src/java/org/apache/log4j/chainsaw/TableColorizingRenderer.java
  
  Index: TableColorizingRenderer.java
  ===================================================================
  RCS file: 
/home/cvs/logging-log4j/src/java/org/apache/log4j/chainsaw/TableColorizingRenderer.java,v
  retrieving revision 1.10
  retrieving revision 1.11
  diff -u -r1.10 -r1.11
  --- TableColorizingRenderer.java      20 Dec 2003 04:51:52 -0000      1.10
  +++ TableColorizingRenderer.java      31 Dec 2003 09:54:40 -0000      1.11
  @@ -95,6 +95,7 @@
     private boolean levelUseIcons = true;
     private DateFormat dateFormatInUse = DATE_FORMATTER;
     private int loggerPrecision = 0;
  +  private boolean toolTipsVisible;
   
     /**
      * Creates a new TableColorizingRenderer object.
  @@ -111,6 +112,10 @@
   
       levelComponent.setText("");
     }
  +  
  +  public void setToolTipsVisible(boolean toolTipsVisible) {
  +      this.toolTipsVisible = toolTipsVisible;
  +  }
   
     public void loadSettings(LoadSettingsEvent event) {
     }
  @@ -180,7 +185,11 @@
             levelComponent.setText("");
           }
   
  -        levelComponent.setToolTipText(value.toString());
  +        if (toolTipsVisible) {
  +            levelComponent.setToolTipText(((JLabel)c).getToolTipText());
  +        } else {
  +            levelComponent.setToolTipText(value.toString());
  +        } 
         } else {
           levelComponent.setIcon(null);
           levelComponent.setText(value.toString());
  
  
  
  1.6       +0 -1      logging-log4j/src/java/org/apache/log4j/chainsaw/JSortTable.java
  
  Index: JSortTable.java
  ===================================================================
  RCS file: 
/home/cvs/logging-log4j/src/java/org/apache/log4j/chainsaw/JSortTable.java,v
  retrieving revision 1.5
  retrieving revision 1.6
  diff -u -r1.5 -r1.6
  --- JSortTable.java   16 Dec 2003 00:45:26 -0000      1.5
  +++ JSortTable.java   31 Dec 2003 09:54:40 -0000      1.6
  @@ -130,7 +130,6 @@
             if ((row > -1) && (row < getRowCount())) {
               try {
                 setRowSelectionInterval(row, row);
  -              scrollRectToVisible(getCellRect(row, col, true));
               } catch (IllegalArgumentException iae) {
               }
                //ignore..out of bounds
  
  
  
  1.11      +24 -0     
logging-log4j/src/java/org/apache/log4j/chainsaw/ApplicationPreferenceModelPanel.java
  
  Index: ApplicationPreferenceModelPanel.java
  ===================================================================
  RCS file: 
/home/cvs/logging-log4j/src/java/org/apache/log4j/chainsaw/ApplicationPreferenceModelPanel.java,v
  retrieving revision 1.10
  retrieving revision 1.11
  diff -u -r1.10 -r1.11
  --- ApplicationPreferenceModelPanel.java      23 Dec 2003 02:18:40 -0000      1.10
  +++ ApplicationPreferenceModelPanel.java      31 Dec 2003 09:54:40 -0000      1.11
  @@ -93,6 +93,7 @@
     private ApplicationPreferenceModel uncommittedPreferenceModel =
       new ApplicationPreferenceModel();
     JTextField identifierExpression;
  +  JTextField toolTipDisplayMillis;    
   
     ApplicationPreferenceModelPanel(ApplicationPreferenceModel model) {
       this.committedPreferenceModel = model;
  @@ -102,6 +103,12 @@
           public void actionPerformed(ActionEvent e) {
             uncommittedPreferenceModel.setIdentifierExpression(
               identifierExpression.getText());
  +            try {
  +                int millis = Integer.parseInt(toolTipDisplayMillis.getText());
  +                if (millis >= 0) {
  +                    uncommittedPreferenceModel.setToolTipDisplayMillis(millis);
  +                }
  +            } catch (NumberFormatException nfe) {}
             committedPreferenceModel.apply(uncommittedPreferenceModel);
             hidePanel();
           }
  @@ -391,6 +398,7 @@
         setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
   
         identifierExpression = new JTextField(20);
  +      toolTipDisplayMillis = new JTextField(8);
   
         Box p = new Box(BoxLayout.X_AXIS);
   
  @@ -423,6 +431,14 @@
         
         add(p2);
         add(p3);
  +
  +      JPanel p4 = new JPanel(new FlowLayout(FlowLayout.LEFT));
  +
  +      p4.add(new JLabel("ToolTip Display (millis)"));
  +      p4.add(Box.createHorizontalStrut(5));
  +      p4.add(toolTipDisplayMillis);
  +      add(p4);
  +
         add(Box.createVerticalGlue());
       }
   
  @@ -484,6 +500,14 @@
               responsiveSlider.setValue(value);
             }
           });
  +
  +        uncommittedPreferenceModel.addPropertyChangeListener(
  +          "toolTipDisplayMillis",
  +          new PropertyChangeListener() {
  +            public void propertyChange(PropertyChangeEvent evt) {
  +              toolTipDisplayMillis.setText(evt.getNewValue().toString());
  +            }
  +          });
   
         showNoReceiverWarning.addActionListener(
           new ActionListener() {
  
  
  
  1.12      +15 -1     
logging-log4j/src/java/org/apache/log4j/chainsaw/ApplicationPreferenceModel.java
  
  Index: ApplicationPreferenceModel.java
  ===================================================================
  RCS file: 
/home/cvs/logging-log4j/src/java/org/apache/log4j/chainsaw/ApplicationPreferenceModel.java,v
  retrieving revision 1.11
  retrieving revision 1.12
  diff -u -r1.11 -r1.12
  --- ApplicationPreferenceModel.java   23 Dec 2003 02:18:40 -0000      1.11
  +++ ApplicationPreferenceModel.java   31 Dec 2003 09:54:40 -0000      1.12
  @@ -73,6 +73,7 @@
       private boolean confirmExit;
       private boolean showSplash;
       private String lookAndFeelClassName;
  +    private int toolTipDisplayMillis;
       
       
       private int responsiveness;
  @@ -163,12 +164,22 @@
           return identifierExpression;
       }
   
  +    public final void setToolTipDisplayMillis(int newToolTipDisplayMillis) {
  +        int oldToolTipDisplayMillis = toolTipDisplayMillis;
  +        toolTipDisplayMillis = newToolTipDisplayMillis;
  +        firePropertyChange("toolTipDisplayMillis", oldToolTipDisplayMillis, 
newToolTipDisplayMillis);
  +    }
  +    
  +    public final int getToolTipDisplayMillis() {
  +        return toolTipDisplayMillis;
  +    }
  +
       public final void setIdentifierExpression(String newIdentifierExpression) {
           String oldIdentifierExpression=identifierExpression;
           this.identifierExpression = newIdentifierExpression;
           firePropertyChange("identifierExpression", oldIdentifierExpression, 
newIdentifierExpression);
       }
  -    
  +
       /**
        * @param showNoReceiverWarning The showNoReceiverWarning to set.
        */
  @@ -192,6 +203,7 @@
          setLookAndFeelClassName(event.getSetting("lookAndFeelClassName"));
          setConfirmExit(event.asBoolean("confirmExit"));
          setShowSplash(event.asBoolean("showSplash"));
  +       setToolTipDisplayMillis(event.asInt("toolTipDisplayMillis"));
       }
   
       /* (non-Javadoc)
  @@ -208,6 +220,7 @@
           event.saveSetting("lookAndFeelClassName", getLookAndFeelClassName());
           event.saveSetting("confirmExit",isConfirmExit());
           event.saveSetting("showSplash", isShowSplash());
  +        event.saveSetting("toolTipDisplayMillis", getToolTipDisplayMillis());
       }
   
       /**
  @@ -226,6 +239,7 @@
         setLookAndFeelClassName(model.getLookAndFeelClassName());
         setConfirmExit(model.isConfirmExit());
         setShowSplash(model.isShowSplash());
  +      setToolTipDisplayMillis(model.getToolTipDisplayMillis());
       }
       /**
        * @return Returns the responsiveness.
  
  
  
  1.13      +8 -8      
logging-log4j/src/java/org/apache/log4j/chainsaw/ChainsawAppenderHandler.java
  
  Index: ChainsawAppenderHandler.java
  ===================================================================
  RCS file: 
/home/cvs/logging-log4j/src/java/org/apache/log4j/chainsaw/ChainsawAppenderHandler.java,v
  retrieving revision 1.12
  retrieving revision 1.13
  diff -u -r1.12 -r1.13
  --- ChainsawAppenderHandler.java      16 Dec 2003 21:56:31 -0000      1.12
  +++ ChainsawAppenderHandler.java      31 Dec 2003 09:54:40 -0000      1.13
  @@ -49,16 +49,8 @@
   
   package org.apache.log4j.chainsaw;
   
  -import org.apache.log4j.AppenderSkeleton;
  -import org.apache.log4j.LogManager;
  -import org.apache.log4j.helpers.LogLog;
  -import org.apache.log4j.net.SocketReceiver;
  -import org.apache.log4j.plugins.PluginRegistry;
  -import org.apache.log4j.spi.LoggingEvent;
  -
   import java.beans.PropertyChangeListener;
   import java.beans.PropertyChangeSupport;
  -
   import java.util.ArrayList;
   import java.util.Collection;
   import java.util.Iterator;
  @@ -66,6 +58,14 @@
   import java.util.Vector;
   
   import javax.swing.event.EventListenerList;
  +
  +import org.apache.log4j.AppenderSkeleton;
  +import org.apache.log4j.LogManager;
  +import org.apache.log4j.helpers.LogLog;
  +import org.apache.log4j.net.SocketReceiver;
  +import org.apache.log4j.plugins.PluginRegistry;
  +import org.apache.log4j.spi.LoggingEvent;
  +import org.apache.log4j.spi.LoggingEventFieldResolver;
   
   
   /**
  
  
  
  1.2       +0 -4      logging-log4j/src/java/org/apache/log4j/rule/ColorRule.java
  
  Index: ColorRule.java
  ===================================================================
  RCS file: /home/cvs/logging-log4j/src/java/org/apache/log4j/rule/ColorRule.java,v
  retrieving revision 1.1
  retrieving revision 1.2
  diff -u -r1.1 -r1.2
  --- ColorRule.java    14 Dec 2003 20:35:08 -0000      1.1
  +++ ColorRule.java    31 Dec 2003 09:54:40 -0000      1.2
  @@ -68,10 +68,6 @@
     private final Color backgroundColor;
     private final String expression;
   
  -  public ColorRule(Rule rule, Color backgroundColor) {
  -    this(null, rule, backgroundColor, null);
  -  }
  -
     public ColorRule(String expression, Rule rule, Color backgroundColor, Color 
foregroundColor) {
       this.expression = expression;
       this.rule = rule;
  
  
  
  1.2       +1 -1      
logging-log4j/src/java/org/apache/log4j/rule/PartialTextMatchRule.java
  
  Index: PartialTextMatchRule.java
  ===================================================================
  RCS file: 
/home/cvs/logging-log4j/src/java/org/apache/log4j/rule/PartialTextMatchRule.java,v
  retrieving revision 1.1
  retrieving revision 1.2
  diff -u -r1.1 -r1.2
  --- PartialTextMatchRule.java 14 Dec 2003 20:35:08 -0000      1.1
  +++ PartialTextMatchRule.java 31 Dec 2003 09:54:40 -0000      1.2
  @@ -49,8 +49,8 @@
   
   package org.apache.log4j.rule;
   
  -import org.apache.log4j.chainsaw.LoggingEventFieldResolver;
   import org.apache.log4j.spi.LoggingEvent;
  +import org.apache.log4j.spi.LoggingEventFieldResolver;
   
   import java.util.Stack;
   
  
  
  
  1.2       +1 -1      
logging-log4j/src/java/org/apache/log4j/rule/LevelInequalityRule.java
  
  Index: LevelInequalityRule.java
  ===================================================================
  RCS file: 
/home/cvs/logging-log4j/src/java/org/apache/log4j/rule/LevelInequalityRule.java,v
  retrieving revision 1.1
  retrieving revision 1.2
  diff -u -r1.1 -r1.2
  --- LevelInequalityRule.java  14 Dec 2003 20:35:08 -0000      1.1
  +++ LevelInequalityRule.java  31 Dec 2003 09:54:40 -0000      1.2
  @@ -51,8 +51,8 @@
   
   import org.apache.log4j.Level;
   import org.apache.log4j.UtilLoggingLevel;
  -import org.apache.log4j.chainsaw.LoggingEventFieldResolver;
   import org.apache.log4j.spi.LoggingEvent;
  +import org.apache.log4j.spi.LoggingEventFieldResolver;
   
   import java.util.Iterator;
   import java.util.LinkedList;
  
  
  
  1.2       +1 -1      
logging-log4j/src/java/org/apache/log4j/rule/ExpressionRuleContext.java
  
  Index: ExpressionRuleContext.java
  ===================================================================
  RCS file: 
/home/cvs/logging-log4j/src/java/org/apache/log4j/rule/ExpressionRuleContext.java,v
  retrieving revision 1.1
  retrieving revision 1.2
  diff -u -r1.1 -r1.2
  --- ExpressionRuleContext.java        14 Dec 2003 20:35:08 -0000      1.1
  +++ ExpressionRuleContext.java        31 Dec 2003 09:54:40 -0000      1.2
  @@ -49,8 +49,8 @@
   
   package org.apache.log4j.rule;
   
  -import org.apache.log4j.chainsaw.LoggingEventFieldResolver;
   import org.apache.log4j.chainsaw.filter.FilterModel;
  +import org.apache.log4j.spi.LoggingEventFieldResolver;
   
   import java.awt.Point;
   import java.awt.event.KeyAdapter;
  
  
  
  1.2       +1 -1      logging-log4j/src/java/org/apache/log4j/rule/EqualsRule.java
  
  Index: EqualsRule.java
  ===================================================================
  RCS file: /home/cvs/logging-log4j/src/java/org/apache/log4j/rule/EqualsRule.java,v
  retrieving revision 1.1
  retrieving revision 1.2
  diff -u -r1.1 -r1.2
  --- EqualsRule.java   14 Dec 2003 20:35:08 -0000      1.1
  +++ EqualsRule.java   31 Dec 2003 09:54:40 -0000      1.2
  @@ -49,8 +49,8 @@
   
   package org.apache.log4j.rule;
   
  -import org.apache.log4j.chainsaw.LoggingEventFieldResolver;
   import org.apache.log4j.spi.LoggingEvent;
  +import org.apache.log4j.spi.LoggingEventFieldResolver;
   
   import java.util.Stack;
   
  
  
  
  1.2       +1 -1      logging-log4j/src/java/org/apache/log4j/rule/NotEqualsRule.java
  
  Index: NotEqualsRule.java
  ===================================================================
  RCS file: /home/cvs/logging-log4j/src/java/org/apache/log4j/rule/NotEqualsRule.java,v
  retrieving revision 1.1
  retrieving revision 1.2
  diff -u -r1.1 -r1.2
  --- NotEqualsRule.java        14 Dec 2003 20:35:08 -0000      1.1
  +++ NotEqualsRule.java        31 Dec 2003 09:54:40 -0000      1.2
  @@ -49,8 +49,8 @@
   
   package org.apache.log4j.rule;
   
  -import org.apache.log4j.chainsaw.LoggingEventFieldResolver;
   import org.apache.log4j.spi.LoggingEvent;
  +import org.apache.log4j.spi.LoggingEventFieldResolver;
   
   import java.util.Stack;
   
  
  
  
  1.2       +1 -1      logging-log4j/src/java/org/apache/log4j/rule/InequalityRule.java
  
  Index: InequalityRule.java
  ===================================================================
  RCS file: 
/home/cvs/logging-log4j/src/java/org/apache/log4j/rule/InequalityRule.java,v
  retrieving revision 1.1
  retrieving revision 1.2
  diff -u -r1.1 -r1.2
  --- InequalityRule.java       14 Dec 2003 20:35:08 -0000      1.1
  +++ InequalityRule.java       31 Dec 2003 09:54:40 -0000      1.2
  @@ -49,8 +49,8 @@
   
   package org.apache.log4j.rule;
   
  -import org.apache.log4j.chainsaw.LoggingEventFieldResolver;
   import org.apache.log4j.spi.LoggingEvent;
  +import org.apache.log4j.spi.LoggingEventFieldResolver;
   
   import java.util.Stack;
   
  
  
  
  1.2       +1 -1      logging-log4j/src/java/org/apache/log4j/rule/ExistsRule.java
  
  Index: ExistsRule.java
  ===================================================================
  RCS file: /home/cvs/logging-log4j/src/java/org/apache/log4j/rule/ExistsRule.java,v
  retrieving revision 1.1
  retrieving revision 1.2
  diff -u -r1.1 -r1.2
  --- ExistsRule.java   14 Dec 2003 20:35:08 -0000      1.1
  +++ ExistsRule.java   31 Dec 2003 09:54:40 -0000      1.2
  @@ -49,8 +49,8 @@
   
   package org.apache.log4j.rule;
   
  -import org.apache.log4j.chainsaw.LoggingEventFieldResolver;
   import org.apache.log4j.spi.LoggingEvent;
  +import org.apache.log4j.spi.LoggingEventFieldResolver;
   
   import java.util.Stack;
   
  
  
  
  1.2       +1 -1      logging-log4j/src/java/org/apache/log4j/rule/LikeRule.java
  
  Index: LikeRule.java
  ===================================================================
  RCS file: /home/cvs/logging-log4j/src/java/org/apache/log4j/rule/LikeRule.java,v
  retrieving revision 1.1
  retrieving revision 1.2
  diff -u -r1.1 -r1.2
  --- LikeRule.java     14 Dec 2003 20:35:08 -0000      1.1
  +++ LikeRule.java     31 Dec 2003 09:54:40 -0000      1.2
  @@ -49,8 +49,8 @@
   
   package org.apache.log4j.rule;
   
  -import org.apache.log4j.chainsaw.LoggingEventFieldResolver;
   import org.apache.log4j.spi.LoggingEvent;
  +import org.apache.log4j.spi.LoggingEventFieldResolver;
   
   import org.apache.oro.text.regex.MalformedPatternException;
   import org.apache.oro.text.regex.Pattern;
  
  
  
  1.1                  
logging-log4j/src/java/org/apache/log4j/spi/LoggingEventFieldResolver.java
  
  Index: LoggingEventFieldResolver.java
  ===================================================================
  /*
   * ============================================================================
   *                   The Apache Software License, Version 1.1
   * ============================================================================
   *
   *    Copyright (C) 1999 The Apache Software Foundation. All rights reserved.
   *
   * Redistribution and use in source and binary forms, with or without modifica-
   * tion, are permitted provided that the following conditions are met:
   *
   * 1. Redistributions of  source code must  retain the above copyright  notice,
   *    this list of conditions and the following disclaimer.
   *
   * 2. Redistributions in binary form must reproduce the above copyright notice,
   *    this list of conditions and the following disclaimer in the documentation
   *    and/or other materials provided with the distribution.
   *
   * 3. The end-user documentation included with the redistribution, if any, must
   *    include  the following  acknowledgment:  "This product includes  software
   *    developed  by the  Apache Software Foundation  (http://www.apache.org/)."
   *    Alternately, this  acknowledgment may  appear in the software itself,  if
   *    and wherever such third-party acknowledgments normally appear.
   *
   * 4. The names "log4j" and  "Apache Software Foundation"  must not be used to
   *    endorse  or promote  products derived  from this  software without  prior
   *    written permission. For written permission, please contact
   *    [EMAIL PROTECTED]
   *
   * 5. Products  derived from this software may not  be called "Apache", nor may
   *    "Apache" appear  in their name,  without prior written permission  of the
   *    Apache Software Foundation.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
   * FITNESS  FOR A PARTICULAR  PURPOSE ARE  DISCLAIMED.  IN NO  EVENT SHALL  THE
   * APACHE SOFTWARE  FOUNDATION  OR ITS CONTRIBUTORS  BE LIABLE FOR  ANY DIRECT,
   * INDIRECT, INCIDENTAL, SPECIAL,  EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLU-
   * DING, BUT NOT LIMITED TO, PROCUREMENT  OF SUBSTITUTE GOODS OR SERVICES; LOSS
   * OF USE, DATA, OR  PROFITS; OR BUSINESS  INTERRUPTION)  HOWEVER CAUSED AND ON
   * ANY  THEORY OF LIABILITY,  WHETHER  IN CONTRACT,  STRICT LIABILITY,  OR TORT
   * (INCLUDING  NEGLIGENCE OR  OTHERWISE) ARISING IN  ANY WAY OUT OF THE  USE OF
   * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   *
   * This software  consists of voluntary contributions made  by many individuals
   * on  behalf of the Apache Software  Foundation.  For more  information on the
   * Apache Software Foundation, please see <http://www.apache.org/>.
   *
   */
  
  package org.apache.log4j.spi;
  
  import java.util.ArrayList;
  import java.util.List;
  import java.util.StringTokenizer;
  
  
  
  /**
   * A singleton helper utility which accepts a field name and a LoggingEvent and 
returns the
   * String value of that field.
   *
   * This class defines a grammar used in creation of an expression-based Rule.
   *
   * The only available method is Object getField(String fieldName, LoggingEvent 
event).
   *
   * Here is a description of the mapping of field names in the grammar
   * to fields on the logging event.  While the getField method returns an Object, the
   * individual types returned per field are described here:
   *
   * Field Name                Field value (String representation                
Return type
   * LOGGER                    category name (logger)                            String
   * LEVEL                     level                                             Level
   * CLASS                     locationInformation's class name                  String
   * FILE                      locationInformation's file name                   String
   * LINE                      locationInformation's line number                 String
   * METHOD                    locationInformation's method name                 String
   * MSG                       message                                           Object
   * NDC                       NDC                                               String
   * EXCEPTION                 throwable string representation                   
ThrowableInformation
   * TIMESTAMP                 timestamp                                         Long
   * THREAD                    thread                                            String
   * MDC.keyName               entry in the MDC hashtable                        Object
   *                           mapped to key [keyName]
   * PROP.keyName              entry in the Property hashtable                   String
   *                           mapped to the key [keyName]
  
   * NOTE:  the values for the 'keyName' portion of the MDC and PROP mappings must
   * be an exact match to the key in the hashTable (case sensitive).
   *
   * If the passed-in field is null or doesn't match an entry in the above-described
   * mapping, an exception is thrown.
   *
   * @author Scott Deboy <[EMAIL PROTECTED]>
   * @author Paul Smith <[EMAIL PROTECTED]>
   *
   */
  public final class LoggingEventFieldResolver {
    public static final List keywordList = new ArrayList();
    public static final String LOGGER_FIELD = "LOGGER";
    public static final String LEVEL_FIELD = "LEVEL";
    public static final String CLASS_FIELD = "CLASS";
    public static final String FILE_FIELD = "FILE";
    public static final String LINE_FIELD = "LINE";
    public static final String METHOD_FIELD = "METHOD";
    public static final String MSG_FIELD = "MSG";
    public static final String NDC_FIELD = "NDC";
    public static final String EXCEPTION_FIELD = "EXCEPTION";
    public static final String TIMESTAMP_FIELD = "TIMESTAMP";
    public static final String THREAD_FIELD = "THREAD";
    public static final String MDC_FIELD = "MDC.";
    public static final String PROP_FIELD = "PROP.";
    public static final String EMPTY_STRING = "";
    private static final LoggingEventFieldResolver resolver =
      new LoggingEventFieldResolver();
  
    private LoggingEventFieldResolver() {
      keywordList.add(LOGGER_FIELD);
      keywordList.add(LEVEL_FIELD);
      keywordList.add(CLASS_FIELD);
      keywordList.add(FILE_FIELD);
      keywordList.add(LINE_FIELD);
      keywordList.add(METHOD_FIELD);
      keywordList.add(MSG_FIELD);
      keywordList.add(NDC_FIELD);
      keywordList.add(EXCEPTION_FIELD);
      keywordList.add(TIMESTAMP_FIELD);
      keywordList.add(THREAD_FIELD);
      keywordList.add(MDC_FIELD);
      keywordList.add(PROP_FIELD);
    }
    
    public String applyFields(String replaceText, LoggingEvent event) {
          if (replaceText == null) {
                return null;
          }
        StringTokenizer tokenizer = new StringTokenizer(replaceText);
        StringBuffer result = new StringBuffer();
        
        while (tokenizer.hasMoreTokens()) {
            String token = tokenizer.nextToken();
            if (isField(token)  || (token.toUpperCase().startsWith(MDC_FIELD) || 
token.toUpperCase().startsWith(PROP_FIELD))) {
                result.append(getValue(token, event).toString());
            } else { 
                result.append(token);
            }
        }
        return result.toString();
    }
  
    public static LoggingEventFieldResolver getInstance() {
      return resolver;
    }
  
    public boolean isField(String fieldName) {
      return ((fieldName != null) && (keywordList.contains(fieldName.toUpperCase())));
    }
  
    public Object getValue(String fieldName, LoggingEvent event) {
      String upperField = fieldName.toUpperCase();
      LocationInfo info = null;
      if (event.locationInformationExists()) {
          info = event.getLocationInformation();
      }
      if (LOGGER_FIELD.equals(upperField)) {
        return event.getLoggerName();
      } else if (LEVEL_FIELD.equals(upperField)) {
        return event.getLevel();
      } else if (CLASS_FIELD.equals(upperField)) {
        return ((info == null) ? "" : info.getClassName());
      } else if (FILE_FIELD.equals(upperField)) {
        return ((info == null) ? "" : info.getFileName());
      } else if (LINE_FIELD.equals(upperField)) {
        return ((info == null) ? "" : info.getLineNumber());
      } else if (METHOD_FIELD.equals(upperField)) {
        return ((info == null) ? "" : info.getMethodName());
      } else if (MSG_FIELD.equals(upperField)) {
        return event.getMessage();
      } else if (NDC_FIELD.equals(upperField)) {
        String ndcValue = event.getNDC();
        return ((ndcValue == null) ? "" : ndcValue);
      } else if (EXCEPTION_FIELD.equals(upperField)) {
        return event.getThrowableInformation();
      } else if (TIMESTAMP_FIELD.equals(upperField)) {
        return new Long(event.timeStamp);
      } else if (THREAD_FIELD.equals(upperField)) {
        return event.getThreadName();
      } else if (upperField.startsWith(MDC_FIELD)) {
        //note: need to use actual fieldname since case matters
        Object mdcValue = event.getMDC(fieldName.substring(4));
  
        return ((mdcValue == null) ? EMPTY_STRING : mdcValue.toString());
      } else if (upperField.startsWith(PROP_FIELD)) {
        //note: need to use actual fieldname since case matters
        String propValue = event.getProperty(fieldName.substring(5));
        return ((propValue == null) ? EMPTY_STRING : propValue);
      }
  
      //there wasn't a match, so throw a runtime exception
      throw new RuntimeException("Unsupported field name: " + fieldName);
    }
  }
  
  
  
  1.13      +2 -2      
logging-log4j/src/java/org/apache/log4j/chainsaw/prefs/default.properties
  
  Index: default.properties
  ===================================================================
  RCS file: 
/home/cvs/logging-log4j/src/java/org/apache/log4j/chainsaw/prefs/default.properties,v
  retrieving revision 1.12
  retrieving revision 1.13
  diff -u -r1.12 -r1.13
  --- default.properties        23 Dec 2003 02:18:40 -0000      1.12
  +++ default.properties        31 Dec 2003 09:54:40 -0000      1.13
  @@ -9,13 +9,12 @@
   main.window.width=640
   main.window.height=480
   tabPlacement=3
  -Responsiveness=1
  +Responsiveness=3
   
table.columns.order=ID,Timestamp,Level,Logger,Thread,Message,NDC,MDC,Throwable,Class,Method,File,Line,Properties
   table.columns.widths=50,150,50,100,150,300,150,100,300,150,100,100,100,100
   statusBar=true
   receivers=true
   toolbar=true
  -level.display=icons
   SavedConfigs.Size=0
   DateFormat.1=HH:mm:ss
   DateFormat.2=HH:mm
  @@ -24,6 +23,7 @@
   identifierExpression=PROP.hostname - PROP.application
   lookAndFeelClassName=
   confirmExit=true
  +toolTipDisplayMillis=4000
   
   # These are the default LogPanel settings
   dateFormatPattern=ISO8601
  
  
  

---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to