Commits

Christian Fischer committed 1083c58

LogCat: multiple devices support: added device status icon

  • Participants
  • Parent commits 448fda7

Comments (0)

Files changed (1)

File logcat_multiple_devices.patch

 # Parent 10794bc93b5baec24a9163429fed2daa2370e6b4
 LogCat: Added support for multiple devices
 
-diff -r 10794bc93b5b logcat/nbproject/project.xml
---- a/logcat/nbproject/project.xml	Sun Jul 03 00:19:14 2011 +0200
-+++ b/logcat/nbproject/project.xml	Sun Jul 03 01:01:20 2011 +0200
+diff --git a/logcat/nbproject/project.xml b/logcat/nbproject/project.xml
+--- a/logcat/nbproject/project.xml
++++ b/logcat/nbproject/project.xml
 @@ -98,6 +98,14 @@
                      </run-dependency>
                  </dependency>
                      <code-name-base>org.openide.windows</code-name-base>
                      <build-prerequisite/>
                      <compile-dependency/>
-diff -r 10794bc93b5b logcat/src/org/nyerel/nbandroid/logcat/LogDevicesComboBoxSupport.java
---- /dev/null	Thu Jan 01 00:00:00 1970 +0000
-+++ b/logcat/src/org/nyerel/nbandroid/logcat/LogDevicesComboBoxSupport.java	Sun Jul 03 01:01:20 2011 +0200
-@@ -0,0 +1,354 @@
+diff --git a/logcat/src/org/nyerel/nbandroid/logcat/Bundle.properties b/logcat/src/org/nyerel/nbandroid/logcat/Bundle.properties
+--- a/logcat/src/org/nyerel/nbandroid/logcat/Bundle.properties
++++ b/logcat/src/org/nyerel/nbandroid/logcat/Bundle.properties
+@@ -40,3 +40,4 @@
+ LogTopComponent.btLogLevelWarn.actionCommand=WARN
+ LogTopComponent.btLogLevelError.actionCommand=ERROR
+ LogTopComponent.btLogLevelAssert.actionCommand=ASSERT
++LogTopComponent.lDeviceStatusIcon.text_1=
+diff --git a/logcat/src/org/nyerel/nbandroid/logcat/LogDevicesComboBoxSupport.java b/logcat/src/org/nyerel/nbandroid/logcat/LogDevicesComboBoxSupport.java
+new file mode 100644
+--- /dev/null
++++ b/logcat/src/org/nyerel/nbandroid/logcat/LogDevicesComboBoxSupport.java
+@@ -0,0 +1,351 @@
 +/*
 + * Licensed under the Apache License, Version 2.0 (the "License");
 + * you may not use this file except in compliance with the License.
 +                deviceLabels.put(serial, deviceModelName + " [" + serial + "]");
 +                break;
 +            }
-+
-+            // clear the last label, when no new label was found
-+            deviceLabels.remove(serial);
 +        }
 +        while(false);
 +
 +        // do nothing
 +    }
 +}
-diff -r 10794bc93b5b logcat/src/org/nyerel/nbandroid/logcat/LogReader.java
---- a/logcat/src/org/nyerel/nbandroid/logcat/LogReader.java	Sun Jul 03 00:19:14 2011 +0200
-+++ b/logcat/src/org/nyerel/nbandroid/logcat/LogReader.java	Sun Jul 03 01:01:20 2011 +0200
-@@ -22,6 +22,8 @@
+diff --git a/logcat/src/org/nyerel/nbandroid/logcat/LogReader.java b/logcat/src/org/nyerel/nbandroid/logcat/LogReader.java
+--- a/logcat/src/org/nyerel/nbandroid/logcat/LogReader.java
++++ b/logcat/src/org/nyerel/nbandroid/logcat/LogReader.java
+@@ -22,7 +22,11 @@
  import com.android.ddmlib.MultiLineReceiver;
  import com.android.ddmlib.ShellCommandUnresponsiveException;
  import com.android.ddmlib.TimeoutException;
 +import java.beans.PropertyChangeListener;
 +import java.beans.PropertyChangeSupport;
  import java.io.IOException;
++import java.util.ArrayList;
++import java.util.Collection;
  import java.util.HashMap;
  import java.util.HashSet;
-@@ -42,6 +44,10 @@
+ import java.util.Map;
+@@ -42,9 +46,19 @@
   */
  public class LogReader {
      
      private static final Logger LOG = Logger.getLogger(LogReader.class.getName());
      private static final Set<String> ignoredLines;
  
-@@ -54,7 +60,9 @@
++    public static enum CurrentDeviceState {
++        ONLINE,
++        OFFLINE,
++        DISCONNECTED,
++    }
++    
+     static {
+ 
+        // These messages will not be added to log as events
+@@ -54,7 +68,9 @@
      }
  
      private Set<LogListener> listeners;
 -    private IDevice device;
-+    private PropertyChangeSupport changeSupprt;
++    private PropertyChangeSupport changeSupport;
 +    private IDevice currentDevice;
 +    private String requestedDeviceSerial;
      private LogCatOutputReceiver receiver;
      private LogEventInfo lastLogEventInfo;
      private static Pattern sLogPattern = Pattern.compile(
-@@ -66,17 +74,17 @@
+@@ -66,17 +82,17 @@
      private Timer checkReadingStatusTimer;
      private int checkingPeriod = 10000;
      
      
 -    private Map<IDevice,Map<Integer, String[]>> processNameCache
 -            = new HashMap<IDevice, Map<Integer, String[]>>();
-+    private Map<String,Set<LogEvent>> logEventCache
-+            = new HashMap<String,Set<LogEvent>>();
++    private Map<String, Collection<LogEvent>> logEventCache
++            = new HashMap<String, Collection<LogEvent>>();
  
  
      public LogReader() {
          
-+        changeSupprt = new PropertyChangeSupport(this);
-         listeners    = new HashSet<LogListener>();
+-        listeners    = new HashSet<LogListener>();
 -        loggedEvents = new HashSet<LogEvent>();
++        changeSupport = new PropertyChangeSupport(this);
++        listeners     = new HashSet<LogListener>();
          
          adb = AndroidDebugBridgeFactory.getDefault();
          checkReadingStatusTimer = new Timer();
-@@ -94,17 +102,53 @@
+@@ -94,17 +110,80 @@
              }
          }, checkingPeriod, checkingPeriod);
      }
 +    }
 +    
 +    
-+    public Set<LogEvent> getLogEventsForDevice(String device) {
++    public Collection<LogEvent> getLogEventsForDevice(String device) {
 +        return logEventCache.get(device);
 +    }
 +    
 +            // set new device
 +            this.requestedDeviceSerial = device;
 +            
-+            // stop reading on current device
-+            stopReading();
++            if (receiver == null) {
++                startReading();
++            }
++            else {
++                // stop reading on current device,
++                // reading will be restarted 
++                stopReading();
++            }
 +        }
 +        
 +        return;
 +    }
++    
++    
++    public CurrentDeviceState getCurrentDeviceState() {
++        // device is currently unavailable
++        if (currentDevice == null) {
++            return CurrentDeviceState.DISCONNECTED;
++        }
++        
++        // device is offline
++        if (!deviceReallyConnected()) {
++            return CurrentDeviceState.OFFLINE;
++        }
++        
++        // is currently receiving events?
++        if (receiver == null) {
++            return CurrentDeviceState.OFFLINE;
++        }
++        
++        // device is ready!
++        return CurrentDeviceState.ONLINE;
++    }
  
      private boolean deviceReallyConnected() {
 -        if (adb == null || !adb.isConnected() || !reading || device == null || 
          for (IDevice d: adb.getDevices()) {
              LOG.info("device: " + d.getSerialNumber());
 -            if (d.equals(device)) {
-+            if (d.equals(currentDevice)) {
++            if (d.getSerialNumber().equals(currentDevice.getSerialNumber())) {
                  gotIt = true;
              }
          }
-@@ -114,6 +158,19 @@
+@@ -114,6 +193,19 @@
          }
          return true;
      }
 +    
 +    
 +    public void addPropertyChangeListener(PropertyChangeListener listener) {
-+        this.changeSupprt.addPropertyChangeListener(listener);
++        this.changeSupport.addPropertyChangeListener(listener);
 +    }
 +    
 +    public void removePropertyChangeListener(PropertyChangeListener listener) {
-+        this.changeSupprt.removePropertyChangeListener(listener);
++        this.changeSupport.removePropertyChangeListener(listener);
 +    }
 +    
 +    private void firePropertyChange(String property, Object oldVal, Object newVal) {
-+        this.changeSupprt.firePropertyChange(property, oldVal, newVal);
++        this.changeSupport.firePropertyChange(property, oldVal, newVal);
 +    }
  
      public void addLogListener(LogListener listener) {
          listeners.add(listener);
-@@ -151,17 +208,19 @@
+@@ -151,17 +243,19 @@
  
      private final class LogCatOutputReceiver extends MultiLineReceiver {
  
              }
          }
  
-@@ -190,30 +249,45 @@
+@@ -190,30 +284,51 @@
      }
  
      private void reallyStartReading() {
 -            device = devs[0];
 -        } else {
 -            errorMessage("No devices found!");
+-            return;
 +        if (requestedDeviceSerial == null) {
++            currentDevice = null;
++            
 +            // if no device was requested, select the first available
 +            IDevice[] devs = adb.getDevices();
 +            if (devs != null && devs.length > 0) {
 +                requestedDeviceSerial = devs[0].getSerialNumber();
-+            } else {
-+                errorMessage("No devices found!");
++            }
++            else {
++                // no devices available - announce the current state and stop here
++                changeSupport.firePropertyChange(PROPERTY_CURRENT_DEVICE_STATE, null, getCurrentDeviceState());
 +                return;
 +            }
-+        }
-+        
+         }
+         
+-        if (device.isOnline()) {
 +        // select current device by requested serial
 +        if (currentDevice == null || !currentDevice.getSerialNumber().equals(requestedDeviceSerial)) {
 +            currentDevice =  null;
-+            
+             
+-           // Clear log on newly connected devices
+-            if(lastDeviceSN != null && !lastDeviceSN.equals(device.getSerialNumber())) {
+-                
+-                loggedEvents.clear();
+-                lastDeviceSN = device.getSerialNumber();
 +            IDevice[] devs = adb.getDevices();
 +            if (devs != null) {
 +                for(IDevice dev : devs) {
 +                        break;
 +                    }
 +                }
-+            }
+             }
+-            
+-            receiver = new LogCatOutputReceiver();
++
++            // notify all clients, the selected device has changed
++            changeSupport.firePropertyChange(PROPERTY_CURRENT_DEVICE, null, requestedDeviceSerial);
 +        }
 +        
-+        if (currentDevice == null) {
-+            errorMessage("Device with serial " + requestedDeviceSerial + " not available.");
-             return;
-         }
-         
--        if (device.isOnline()) {
--            
--           // Clear log on newly connected devices
--            if(lastDeviceSN != null && !lastDeviceSN.equals(device.getSerialNumber())) {
--                
--                loggedEvents.clear();
--                lastDeviceSN = device.getSerialNumber();
--            }
--            
--            receiver = new LogCatOutputReceiver();
-+        if (currentDevice.isOnline()) {
++        if (currentDevice != null && !currentDevice.isOffline()) {
 +            receiver = new LogCatOutputReceiver(currentDevice);
              RequestProcessor.getDefault().post(new Runnable() {
  
                  @Override public void run() {
++                    reading = true;
++
++                    // announce the new device state
++                    changeSupport.firePropertyChange(PROPERTY_CURRENT_DEVICE_STATE, null, getCurrentDeviceState());
++
                      try {
-                         reading = true;
+-                        reading = true;
 -                        device.executeShellCommand("logcat -v long", receiver);
 +                        currentDevice.executeShellCommand("logcat -v long", receiver, 0);
                      } catch (TimeoutException e) {
                        LOG.log(Level.FINE, null, e);
                        reading = false;
-@@ -228,6 +302,11 @@
+@@ -228,16 +343,25 @@
                        reading = false;
                      } finally {
                          receiver = null;
 +                        
++                        // announce the new device state
++                        changeSupport.firePropertyChange(PROPERTY_CURRENT_DEVICE_STATE, null, getCurrentDeviceState());
++                        
 +                        // when the device has changed, we restart the logging process
 +                        if (!currentDevice.getSerialNumber().equals(requestedDeviceSerial)) {
 +                            startReading();
                      }
                  }
              });
-@@ -237,7 +316,10 @@
+-        } else {
+-            errorMessage("Device is offline! (if it's connected, try reconnecting it)");
+         }
      }
  
      public void stopReading() {
          shouldBeReading = false;
      }
  
-@@ -248,7 +330,7 @@
+@@ -248,7 +372,7 @@
          return false;
      }
  
          for (String line : lines) {
              // ignore empty lines.
              if (line.length() > 0) {
-@@ -286,6 +368,16 @@
+@@ -286,6 +410,16 @@
                      }
                      
                      LogEvent event = new LogEvent(lastLogEventInfo, message);
 +                    
-+                    Set<LogEvent> loggedEvents = logEventCache.get(device.getSerialNumber());
++                    Collection<LogEvent> loggedEvents = logEventCache.get(device.getSerialNumber());
 +                    if (loggedEvents == null) {
-+                        loggedEvents = new HashSet<LogEvent>();
++                        loggedEvents = new ArrayList<LogEvent>();
 +                        logEventCache.put(device.getSerialNumber(), loggedEvents);
 +                        
 +                        // notify listeners for the new device
                      if(!loggedEvents.contains(event)) {
                          loggedEvents.add(event);
                          sendNewLogEvent(event);
-@@ -306,10 +398,10 @@
+@@ -306,10 +440,10 @@
       *               or {@code null}, if the process couldn't be retrieved yet.
       */
      private String[] getProcessName(IDevice device, int pid) {
          }
  
          String[] nameref = cache.get(pid);
-@@ -330,13 +422,4 @@
+@@ -330,13 +464,4 @@
  
          return nameref;
      }
 -        loggedEvents.clear();
 -    }
  }
-diff -r 10794bc93b5b logcat/src/org/nyerel/nbandroid/logcat/LogTopComponent.form
---- a/logcat/src/org/nyerel/nbandroid/logcat/LogTopComponent.form	Sun Jul 03 00:19:14 2011 +0200
-+++ b/logcat/src/org/nyerel/nbandroid/logcat/LogTopComponent.form	Sun Jul 03 01:01:20 2011 +0200
-@@ -171,12 +171,7 @@
+diff --git a/logcat/src/org/nyerel/nbandroid/logcat/LogTopComponent.form b/logcat/src/org/nyerel/nbandroid/logcat/LogTopComponent.form
+--- a/logcat/src/org/nyerel/nbandroid/logcat/LogTopComponent.form
++++ b/logcat/src/org/nyerel/nbandroid/logcat/LogTopComponent.form
+@@ -171,10 +171,16 @@
  
        <Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
        <SubComponents>
 -        <Component class="javax.swing.JComboBox" name="cmbSelectDevice">
--          <Properties>
++        <Component class="javax.swing.JLabel" name="lDeviceStatusIcon">
+           <Properties>
 -            <Property name="model" type="javax.swing.ComboBoxModel" editor="org.netbeans.modules.form.editors2.ComboBoxModelEditor">
 -              <StringArray count="0"/>
--            </Property>
--          </Properties>
++            <Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
++              <Image iconType="3" name="/org/nyerel/nbandroid/logcat/resources/device_status_disconnected.png"/>
++            </Property>
++            <Property name="labelFor" type="java.awt.Component" editor="org.netbeans.modules.form.ComponentChooserEditor">
++              <ComponentRef name="cmbLogDevices"/>
++            </Property>
++            <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
++              <ResourceString bundle="org/nyerel/nbandroid/logcat/Bundle.properties" key="LogTopComponent.lDeviceStatusIcon.text_1" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
+             </Property>
+           </Properties>
+           <Constraints>
+@@ -183,6 +189,13 @@
+             </Constraint>
+           </Constraints>
+         </Component>
 +        <Component class="javax.swing.JComboBox" name="cmbLogDevices">
-           <Constraints>
-             <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
-               <GridBagConstraints gridX="-1" gridY="-1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="10" weightX="0.0" weightY="0.0"/>
-diff -r 10794bc93b5b logcat/src/org/nyerel/nbandroid/logcat/LogTopComponent.java
---- a/logcat/src/org/nyerel/nbandroid/logcat/LogTopComponent.java	Sun Jul 03 00:19:14 2011 +0200
-+++ b/logcat/src/org/nyerel/nbandroid/logcat/LogTopComponent.java	Sun Jul 03 01:01:20 2011 +0200
-@@ -84,7 +84,9 @@
++          <Constraints>
++            <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
++              <GridBagConstraints gridX="-1" gridY="-1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="10" weightX="0.0" weightY="0.0"/>
++            </Constraint>
++          </Constraints>
++        </Component>
+         <Component class="javax.swing.JTextField" name="txtFilterText">
+           <Properties>
+             <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+diff --git a/logcat/src/org/nyerel/nbandroid/logcat/LogTopComponent.java b/logcat/src/org/nyerel/nbandroid/logcat/LogTopComponent.java
+--- a/logcat/src/org/nyerel/nbandroid/logcat/LogTopComponent.java
++++ b/logcat/src/org/nyerel/nbandroid/logcat/LogTopComponent.java
+@@ -16,8 +16,11 @@
+ import com.android.ddmlib.Log.LogLevel;
+ import java.awt.event.MouseAdapter;
+ import java.awt.event.MouseEvent;
++import java.beans.PropertyChangeEvent;
++import java.beans.PropertyChangeListener;
+ import java.io.IOException;
+ import java.util.ArrayList;
++import java.util.Collection;
+ import org.nyerel.nbandroid.logcat.logtable.LogTableModel;
+ import org.nyerel.nbandroid.logcat.logtable.LogTableManager;
+ import org.nyerel.nbandroid.logcat.logtable.LogTableCellRenderer;
+@@ -28,12 +31,12 @@
+ import java.util.logging.Level;
+ import java.util.logging.Logger;
+ import javax.swing.AbstractButton;
++import javax.swing.ImageIcon;
+ import javax.swing.JScrollPane;
+ import javax.swing.JTable;
+ import javax.swing.JViewport;
+ import javax.swing.event.ChangeListener;
+ import javax.swing.table.TableColumn;
+-import javax.swing.table.TableModel;
+ import javax.swing.table.TableRowSorter;
+ import javax.swing.text.Document;
+ import org.netbeans.api.java.classpath.GlobalPathRegistry;
+@@ -52,6 +55,7 @@
+ import org.openide.text.Line;
+ import org.openide.text.Line.ShowOpenType;
+ import org.openide.text.Line.ShowVisibilityType;
++import org.openide.util.WeakListeners;
+ 
+ /**
+  * Top component which displays something.
+@@ -84,7 +88,53 @@
      
      private List<LogTableManager> tabManagers;
      private LogLineRowFilter rowFilter = new LogLineRowFilter();
--
 +    
 +    private LogDevicesComboBoxSupport cmbLogDevicesSupport;
 +    
++    
++    private PropertyChangeListener myPropertyChangeListener = new PropertyChangeListener() {
++        @Override
++        public void propertyChange(PropertyChangeEvent evt) {
++            if (LogReader.PROPERTY_CURRENT_DEVICE.equals(evt.getPropertyName())) {
++                String newDeviceSerial = reader.getCurrentDevice();
++                Collection<LogEvent> events = reader.getLogEventsForDevice(newDeviceSerial);
+ 
++                if (events != null) {
++                    for(LogTableManager manager : tabManagers) {
++                        // clear all messages
++                        manager.getModel().clear();
++
++                        // add all events of the new device
++                        manager.addAllEvents(events);
++                    }
++                }
++            }
++            
++            if (LogReader.PROPERTY_CURRENT_DEVICE_STATE.equals(evt.getPropertyName())) {
++                String res;
++                
++                switch((LogReader.CurrentDeviceState)evt.getNewValue()) {
++                case ONLINE:
++                    res = "/org/nyerel/nbandroid/logcat/resources/device_status_online.png";
++                    break;
++
++                case OFFLINE:
++                    res = "/org/nyerel/nbandroid/logcat/resources/device_status_offline.png";
++                    break;
++
++                default:
++                    res = "/org/nyerel/nbandroid/logcat/resources/device_status_disconnected.png";
++                    break;
++                }
++                
++                lDeviceStatusIcon.setIcon(new ImageIcon(getClass().getResource(res)));
++            }
++
++            return;
++        }
++    };
++    
++    
      public LogTopComponent() {
          initComponents();
          setName(NbBundle.getMessage(LogTopComponent.class, "CTL_LogTopComponent"));
-@@ -151,6 +153,11 @@
+@@ -151,6 +201,11 @@
        if (reader != null) {
          stopReading();
        }
        super.componentClosed();
      }
  
-@@ -270,6 +277,9 @@
+@@ -270,6 +325,11 @@
                  for (LogTableManager manager : tabManagers) {
                      reader.addLogListener(manager);
                  }
 +                
++                reader.addPropertyChangeListener(WeakListeners.propertyChange(myPropertyChangeListener, reader));
++                
 +                cmbLogDevicesSupport = new LogDevicesComboBoxSupport(reader);
 +                cmbLogDevicesSupport.attach(cmbLogDevices);
              }
              
              timer.schedule(new TimerTask() {
-@@ -316,7 +326,7 @@
+@@ -316,7 +376,8 @@
          autoScrollToggleButton = new javax.swing.JToggleButton();
          clearButton = new javax.swing.JButton();
          panFilterTools = new javax.swing.JPanel();
 -        cmbSelectDevice = new javax.swing.JComboBox();
++        lDeviceStatusIcon = new javax.swing.JLabel();
 +        cmbLogDevices = new javax.swing.JComboBox();
          txtFilterText = new javax.swing.JTextField();
          tbLogLevelSelect = new javax.swing.JToolBar();
          btLogLevelVerbose = new javax.swing.JToggleButton();
-@@ -412,8 +422,7 @@
-         add(tbLeft, gridBagConstraints);
+@@ -413,7 +474,11 @@
  
          panFilterTools.setLayout(new java.awt.GridBagLayout());
--
+ 
 -        panFilterTools.add(cmbSelectDevice, new java.awt.GridBagConstraints());
++        lDeviceStatusIcon.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/nyerel/nbandroid/logcat/resources/device_status_disconnected.png"))); // NOI18N
++        lDeviceStatusIcon.setLabelFor(cmbLogDevices);
++        org.openide.awt.Mnemonics.setLocalizedText(lDeviceStatusIcon, org.openide.util.NbBundle.getMessage(LogTopComponent.class, "LogTopComponent.lDeviceStatusIcon.text_1")); // NOI18N
++        panFilterTools.add(lDeviceStatusIcon, new java.awt.GridBagConstraints());
 +        panFilterTools.add(cmbLogDevices, new java.awt.GridBagConstraints());
  
          txtFilterText.setText(org.openide.util.NbBundle.getMessage(LogTopComponent.class, "LogTopComponent.txtFilterText.text")); // NOI18N
          txtFilterText.addKeyListener(new java.awt.event.KeyAdapter() {
-@@ -624,7 +633,7 @@
+@@ -624,9 +689,10 @@
      private javax.swing.JToggleButton btLogLevelVerbose;
      private javax.swing.JToggleButton btLogLevelWarn;
      private javax.swing.JButton clearButton;
 +    private javax.swing.JComboBox cmbLogDevices;
      private javax.swing.JScrollPane jScrollPane1;
      private javax.swing.JTable jTable1;
++    private javax.swing.JLabel lDeviceStatusIcon;
      private javax.swing.JPanel panCenter;
-@@ -692,6 +701,9 @@
+     private javax.swing.JPanel panFilterTools;
+     private javax.swing.JButton removeTabButton;
+@@ -692,6 +758,9 @@
                public void rowsInserted(int firstRow, int endRow) {
                  try {
                    super.rowsInserted(firstRow, endRow);
                  } catch (IndexOutOfBoundsException ex) {
                    LOG.log(Level.INFO, 
                        "Ignoring exception caused by http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6582564", ex);
+diff --git a/logcat/src/org/nyerel/nbandroid/logcat/logtable/LogTableManager.java b/logcat/src/org/nyerel/nbandroid/logcat/logtable/LogTableManager.java
+--- a/logcat/src/org/nyerel/nbandroid/logcat/logtable/LogTableManager.java
++++ b/logcat/src/org/nyerel/nbandroid/logcat/logtable/LogTableManager.java
+@@ -15,6 +15,7 @@
+ 
+ import org.nyerel.nbandroid.logcat.*;
+ import java.awt.Rectangle;
++import java.util.Collection;
+ import javax.swing.JTable;
+ import javax.swing.JToggleButton;
+ import javax.swing.SwingUtilities;
+@@ -84,6 +85,16 @@
+             }
+         });
+     }
++    
++    public void addAllEvents(Collection<LogEvent> events) {
++        for(LogEvent event : events) {
++            model.addNewEvent(event);
++        }
++        
++        if (autoFollowScroll) {
++            scrollToBottom();
++        }
++    }
+ 
+     @Override
+     public void newLogEvent(LogEvent logEvent) {
+diff --git a/logcat/src/org/nyerel/nbandroid/logcat/resources/device_status_disconnected.png b/logcat/src/org/nyerel/nbandroid/logcat/resources/device_status_disconnected.png
+new file mode 100644
+index 0000000000000000000000000000000000000000..b41933d0290a3cfd795d04b3e3a1c9874e2b6e0f
+GIT binary patch
+literal 506
+zc$@+H0R{evP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00001b5ch_0Itp)
+z=>Px#24YJ`L;y_yO#n?P4+J>?000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2ipe&
+z7A*$FS{14Q00DYQL_t(I%axM7P6AO7g}+Jm1qg!Avli0QYC%n<9h+!FLm`#1qLvq+
+z-U45SXk=L+bne__Ebe9Z0*&5gcYe;9pEJVQn@(R0#2beHwGb2-jYbmZ*T!Trv7_hl
+z;qjao5qN!l^K1goPtQ^ljK^aG7!HU0<;`X@0!|VD=NyB<fWzT%k!%=(U}lK;@BQ5!
+z@AEkTNtR`%s_gfB{_|9oEXyoWReHT1yX`Iv<TrP$*mM#t3`o<IJkLo~m9i|s%x~%<
+zHFiZY1Fh5(Y&M%SV630io^$EcDT#BAbx}m=0b~It5vc`ua5?9E2U=Ap@RLfxVR9K&
+z4I@<5517;xCW6FsTLTqoN~u;PS}Cj5if;GXYhDu_tI{kuTGM*{4JO{~_xslAbWRS3
+z05p-BKteYN6h*;ev1l&w>+_5DRXc$2B<Anlj+OASZMpTaSn%81Ufq1F-3sRmBIdm&
+wl)G)MPM+t&<;6gn-Waj+(Fgz^%jF5?7m{As+aL2EMF0Q*07*qoM6N<$g6@FRlmGw#
+
+diff --git a/logcat/src/org/nyerel/nbandroid/logcat/resources/device_status_offline.png b/logcat/src/org/nyerel/nbandroid/logcat/resources/device_status_offline.png
+new file mode 100644
+index 0000000000000000000000000000000000000000..f76f54a1fee900a1f8000172be7dbff1729dc83c
+GIT binary patch
+literal 402
+zc%17D@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|*pj^6T^LpY
+zSqz7-*mwXb&H|6fVg?3oVGw3ym^DWND9B#o>FdgVo1IxqTd4C^S|HFAAx{^_5R21u
+zC;NIaI|{V!pJeGCrj(*D<-&0<#esi<Yv02|+GjLlQWPC0xZLp+o8(ZZmpDUCQ@re`
+z*KJ`H{<e2j#_L~)UY|1I0q+!M3-*s6^=4Q+JetP5O5tPao&9B2)`|LAtPdnZ8tzq`
+zsW*7}opHxD5&s3@p;Q0gn)5@nLeo3Xp-V7>x#g)*eS`4NNm2<cTKx;Ar{0{mlXsQR
+z>MkXQ=;^V0JVblvta`VWfl)&$VRuv^57QruA2I8{&(i<%?aH4Ok8}STv75@gKfw3l
+z`clt3$4tWxSzl<iPo1E7bVvUV`<={&;s<R@4_%dfC$_{u+xWqohTQtf&vy)LcX0V#
+ro^Qw{-Y5Md@zW0fW0tx14KCI&|G8Wys$JT?02G>@u6{1-oD!M<<2#vr
+
+diff --git a/logcat/src/org/nyerel/nbandroid/logcat/resources/device_status_online.png b/logcat/src/org/nyerel/nbandroid/logcat/resources/device_status_online.png
+new file mode 100644
+index 0000000000000000000000000000000000000000..5a1c5c57bfc79a6425ffb41fd39758461c0ac8ff
+GIT binary patch
+literal 407
+zc%17D@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|*pj^6U4Zl|
+z1_s?V$tQs<&H|6fVg?3oVGw3ym^DWND9B#o>FdgVo1IzAT=GZ#qZXhk;+`&!Ar_~5
+zCp&sEI|{hQpPZv?eU!Pw#nCCxs9WYh>+yn#+GkYnq$oO0&{)?r`^Zrh%NCaE1)E-E
+z?q2q`Q&VEq>RtcV-?xo*%97VO{)TH~B9EBhj@|bbYFx@||N5X-V50N>m)dbQ6?MkX
+zMWl7z795a$#5wQct8J&m+TUGcp0ZxGXJV727xUKDn_0wC`dgUOR(xogc6ihF;`y3h
+zU&8_~IPC4e>#F1tT3@{C)<*^ln}cclKTcP2sqZq?J@~C!+EKHy_p<n-1qXh*KKu5M
+zDaWse>5kyboqKL6TO@GCsFg49ydHA#)bVs50jcxH{8y`0T%XK+pGC7t#ICXX-u!!^
+zo6hlGzQSSS|D<h|{^Z@;52w8SIKQy&MH1ha<Vx`%BQ*|Ss4;lD`njxgN@xNAmlL7u
+