FileDocCategorySizeDatePackage
LogPanel.javaAPI DocAndroid 1.5 API52481Wed May 06 22:41:08 BST 2009com.android.ddmuilib.logcat

LogPanel

public class LogPanel extends com.android.ddmuilib.SelectionDependentPanel

Fields Summary
private static final int
STRING_BUFFER_LENGTH
public static final int
FILTER_NONE
no filtering. Only one tab with everything.
public static final int
FILTER_MANUAL
manual mode for filter. all filters are manually created.
public static final int
FILTER_AUTO_PID
automatic mode for filter (pid mode). All filters are automatically created.
public static final int
FILTER_AUTO_TAG
automatic mode for filter (tag mode). All filters are automatically created.
public static final int
FILTER_DEBUG
Manual filtering mode + new filter for debug app, if needed
public static final int
COLUMN_MODE_MANUAL
public static final int
COLUMN_MODE_AUTO
public static String
PREFS_TIME
public static String
PREFS_LEVEL
public static String
PREFS_PID
public static String
PREFS_TAG
public static String
PREFS_MESSAGE
private static Pattern
sLogPattern
This pattern is meant to parse the first line of a log message with the option 'logcat -v long'. The first line represents the date, tag, severity, etc.. while the following lines are the message (can be several line).
This first line looks something like
"[ 00-00 00:00:00.000 <pid>:0x<???> <severity>/<tag>]"
Note: severity is one of V, D, I, W, or EM
Note: the fraction of second value can have any number of digit. Note the tag should be trim as it may have spaces at the end.
private org.eclipse.swt.widgets.Composite
mParent
private org.eclipse.jface.preference.IPreferenceStore
mStore
private org.eclipse.swt.widgets.TabFolder
mFolders
top object in the view
private LogColors
mColors
private ILogFilterStorageManager
mFilterStorage
private LogCatOuputReceiver
mCurrentLogCat
private LogMessage[]
mBuffer
Circular buffer containing the logcat output. This is unfiltered. The valid content goes from mBufferStart to mBufferEnd - 1. Therefore its number of item is mBufferEnd - mBufferStart.
private int
mBufferStart
Represents the oldest message in the buffer
private int
mBufferEnd
Represents the next usable item in the buffer to receive new message. This can be equal to mBufferStart, but when used mBufferStart will be incremented as well.
private LogFilter[]
mFilters
Filter list
private LogFilter
mDefaultFilter
Default filter
private LogFilter
mCurrentFilter
Current filter being displayed
private int
mFilterMode
Filtering mode
private com.android.ddmlib.Device
mCurrentLoggedDevice
Device currently running logcat
private com.android.ddmuilib.actions.ICommonAction
mDeleteFilterAction
private com.android.ddmuilib.actions.ICommonAction
mEditFilterAction
private com.android.ddmuilib.actions.ICommonAction[]
mLogLevelActions
private LogMessageInfo
mLastMessageInfo
pointer to the latest LogMessageInfo. this is used for multi line log message, to reuse the info regarding level, pid, etc...
private boolean
mPendingAsyncRefresh
private com.android.ddmuilib.IImageLoader
mImageLoader
loader for the images. the implementation will varie between standalone app and eclipse plugin app and eclipse plugin.
private String
mDefaultLogSave
private int
mColumnMode
private org.eclipse.swt.graphics.Font
mDisplayFont
private com.android.ddmuilib.ITableFocusListener
mGlobalListener
Constructors Summary
public LogPanel(com.android.ddmuilib.IImageLoader imageLoader, LogColors colors, ILogFilterStorageManager filterStorage, int mode)
Create the log view with some default parameters

param
imageLoader the image loader.
param
colors The display color object
param
filterStorage the storage for user defined filters.
param
mode The filtering mode

        mImageLoader = imageLoader;
        mColors = colors;
        mFilterMode = mode;
        mFilterStorage = filterStorage;
        mStore = DdmUiPreferences.getStore();
    
Methods Summary
public voidaddFilter()
Adds a new Filter. This methods displays the UI to create the filter and set up its parameters.
MUST be called from the ui thread.

        EditFilterDialog dlg = new EditFilterDialog(mImageLoader,
                mFolders.getShell());
        if (dlg.open()) {
            synchronized (mBuffer) {
                // get the new filter in the array
                LogFilter filter = dlg.getFilter();
                addFilterToArray(filter);

                int index = mFilters.length - 1;
                if (mDefaultFilter != null) {
                    index++;
                }

                if (false) {

                    for (LogFilter f : mFilters) {
                        if (f.uiReady()) {
                            f.dispose();
                        }
                    }
                    if (mDefaultFilter != null && mDefaultFilter.uiReady()) {
                        mDefaultFilter.dispose();
                    }

                    // for each filter, create a tab.
                    int i = 0;
                    if (mFilters != null) {
                        for (LogFilter f : mFilters) {
                            createTab(f, i++, true);
                        }
                    }
                    if (mDefaultFilter != null) {
                        createTab(mDefaultFilter, i++, true);
                    }
                } else {

                    // create ui for the filter.
                    createTab(filter, index, true);

                    // reset the default as it shouldn't contain the content of
                    // this new filter.
                    if (mDefaultFilter != null) {
                        initDefaultFilter();
                    }
                }

                // select the new filter
                if (mCurrentFilter != null) {
                    mCurrentFilter.setSelectedState(false);
                }
                mFolders.setSelection(index);
                filter.setSelectedState(true);
                mCurrentFilter = filter;

                selectionChanged(filter);

                // finally we update the filtering mode if needed
                if (mFilterMode == FILTER_NONE) {
                    mFilterMode = FILTER_MANUAL;
                }

                mFilterStorage.saveFilters(mFilters);

            }
        }
    
private voidaddFilterToArray(LogFilter newFilter)
Adds a new filter to the current filter array, and set its colors

param
newFilter The filter to add

        // set the colors
        newFilter.setColors(mColors);

        // add it to the array.
        if (mFilters != null && mFilters.length > 0) {
            LogFilter[] newFilters = new LogFilter[mFilters.length+1];
            System.arraycopy(mFilters, 0, newFilters, 0, mFilters.length);
            newFilters[mFilters.length] = newFilter;
            mFilters = newFilters;
        } else {
            mFilters = new LogFilter[1];
            mFilters[0] = newFilter;
        }
    
private voidaddTableToFocusListener(org.eclipse.swt.widgets.Table table)
Sets up a Table object to notify the global Table Focus listener when it gets or loses the focus.

param
table the Table object.

        // create the activator for this table
        final IFocusedTableActivator activator = new IFocusedTableActivator() {
            public void copy(Clipboard clipboard) {
                copyTable(clipboard, table);
            }

            public void selectAll() {
                table.selectAll();
            }
        };

        // add the focus listener on the table to notify the global listener
        table.addFocusListener(new FocusListener() {
            public void focusGained(FocusEvent e) {
                mGlobalListener.focusGained(activator);
            }

            public void focusLost(FocusEvent e) {
                mGlobalListener.focusLost(activator);
            }
        });
    
private voidasyncRefresh()
Refreshes the UI with new messages.

        if (mFolders.isDisposed() == false) {
            synchronized (mBuffer) {
                try {
                    // the circular buffer has been updated, let have the filter flush their
                    // display with the new messages.
                    if (mFilters != null) {
                        for (LogFilter f : mFilters) {
                            f.flush();
                        }
                    }
    
                    if (mDefaultFilter != null) {
                        mDefaultFilter.flush();
                    }
                } finally {
                    // the pending refresh is done.
                    mPendingAsyncRefresh = false;
                }
            }
        } else {
            stopLogCat(true);
        }
    
private booleancheckFilter(com.android.ddmuilib.logcat.LogPanel$LogMessageInfo md)
Checks if there's an automatic filter for this md and if not adds the filter and the ui. This must be called from the UI!

param
md
return
true if the filter existed already

        if (true)
            return true;
        // look for a filter that matches the pid
        if (mFilterMode == FILTER_AUTO_PID) {
            for (LogFilter f : mFilters) {
                if (f.getPidFilter() == md.pid) {
                    return true;
                }
            }
        } else if (mFilterMode == FILTER_AUTO_TAG) {
            for (LogFilter f : mFilters) {
                if (f.getTagFilter().equals(md.tag)) {
                    return true;
                }
            }
        }

        // if we reach this point, no filter was found.
        // create a filter with a temporary name of the pid
        final LogFilter newFilter = new LogFilter(md.pidString);
        String name = null;
        if (mFilterMode == FILTER_AUTO_PID) {
            newFilter.setPidMode(md.pid);

            // ask the monitor thread if it knows the pid.
            name = mCurrentLoggedDevice.getClientName(md.pid);
        } else {
            newFilter.setTagMode(md.tag);
            name = md.tag;
        }
        addFilterToArray(newFilter);

        final String fname = name;

        // create the tabitem
        final TabItem newTabItem = createTab(newFilter, -1, true);

        // if the name is unknown
        if (fname == null) {
            // we need to find the process running under that pid.
            // launch a thread do a ps on the device
            new Thread("remote PS") { //$NON-NLS-1$
                @Override
                public void run() {
                    // create the receiver
                    PsOutputReceiver psor = new PsOutputReceiver(md.pid,
                            newFilter, newTabItem);

                    // execute ps
                    try {
                        mCurrentLoggedDevice.executeShellCommand("ps", psor); //$NON-NLS-1$
                    } catch (IOException e) {
                        // hmm...
                    }
                }
            }.start();
        }

        return false;
    
public voidclear()
Empty the current circular buffer.

        synchronized (mBuffer) {
            for (int i = 0 ; i < STRING_BUFFER_LENGTH; i++) {
                mBuffer[i] = null;
            }

            mBufferStart = -1;
            mBufferEnd = -1;

            // now we clear the existing filters
            for (LogFilter filter : mFilters) {
                filter.clear();
            }

            // and the default one
            if (mDefaultFilter != null) {
                mDefaultFilter.clear();
            }
        }
    
public voidclientSelected()
Sent when a new client is selected. The new client can be accessed with {@link #getCurrentClient()}.

        // pass
    
public voidcopy(org.eclipse.swt.dnd.Clipboard clipboard)
Copies the current selection of the current filter as multiline text.

param
clipboard The clipboard to place the copied content.

        // get the current table and its selection
        Table currentTable = mCurrentFilter.getTable();

        copyTable(clipboard, currentTable);
    
private static voidcopyTable(org.eclipse.swt.dnd.Clipboard clipboard, org.eclipse.swt.widgets.Table table)
Copies the current selection of a Table into the provided Clipboard, as multi-line text.

param
clipboard The clipboard to place the copied content.
param
table The table to copy from.

        int[] selection = table.getSelectionIndices();

        // we need to sort the items to be sure.
        Arrays.sort(selection);

        // all lines must be concatenated.
        StringBuilder sb = new StringBuilder();

        // loop on the selection and output the file.
        for (int i : selection) {
            TableItem item = table.getItem(i);
            LogMessage msg = (LogMessage)item.getData();
            String line = msg.toString();
            sb.append(line);
            sb.append('\n");
        }

        // now add that to the clipboard
        clipboard.setContents(new Object[] {
            sb.toString()
        }, new Transfer[] {
            TextTransfer.getInstance()
        });
    
protected org.eclipse.swt.widgets.ControlcreateControl(org.eclipse.swt.widgets.Composite parent)
Creates a control capable of displaying some information. This is called once, when the application is initializing, from the UI thread.

        mParent = parent;

        Composite top = new Composite(parent, SWT.NONE);
        top.setLayoutData(new GridData(GridData.FILL_BOTH));
        top.setLayout(new GridLayout(1, false));

        // create the tab folder
        mFolders = new TabFolder(top, SWT.NONE);
        mFolders.setLayoutData(new GridData(GridData.FILL_BOTH));
        mFolders.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                if (mCurrentFilter != null) {
                    mCurrentFilter.setSelectedState(false);
                }
                mCurrentFilter = getCurrentFilter();
                mCurrentFilter.setSelectedState(true);
                updateColumns(mCurrentFilter.getTable());
                if (mCurrentFilter.getTempFilterStatus()) {
                    initFilter(mCurrentFilter);
                }
                selectionChanged(mCurrentFilter);
            }
        });


        Composite bottom = new Composite(top, SWT.NONE);
        bottom.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        bottom.setLayout(new GridLayout(3, false));

        Label label = new Label(bottom, SWT.NONE);
        label.setText("Filter:");

        final Text filterText = new Text(bottom, SWT.SINGLE | SWT.BORDER);
        filterText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        filterText.addModifyListener(new ModifyListener() {
            public void modifyText(ModifyEvent e) {
                updateFilteringWith(filterText.getText());
            }
        });

        /*
        Button addFilterBtn = new Button(bottom, SWT.NONE);
        addFilterBtn.setImage(mImageLoader.loadImage("add.png", //$NON-NLS-1$
                addFilterBtn.getDisplay()));
        */

        // get the filters
        createFilters();

        // for each filter, create a tab.
        int index = 0;

        if (mDefaultFilter != null) {
            createTab(mDefaultFilter, index++, false);
        }

        if (mFilters != null) {
            for (LogFilter f : mFilters) {
                createTab(f, index++, false);
            }
        }

        return top;
    
private voidcreateFilters()

        if (mFilterMode == FILTER_DEBUG || mFilterMode == FILTER_MANUAL) {
            // unarchive the filters.
            mFilters = mFilterStorage.getFilterFromStore();

            // set the colors
            if (mFilters != null) {
                for (LogFilter f : mFilters) {
                    f.setColors(mColors);
                }
            }

            if (mFilterStorage.requiresDefaultFilter()) {
                mDefaultFilter = new LogFilter("Log");
                mDefaultFilter.setColors(mColors);
                mDefaultFilter.setSupportsDelete(false);
                mDefaultFilter.setSupportsEdit(false);
            }
        } else if (mFilterMode == FILTER_NONE) {
            // if the filtering mode is "none", we create a single filter that
            // will receive all
            mDefaultFilter = new LogFilter("Log");
            mDefaultFilter.setColors(mColors);
            mDefaultFilter.setSupportsDelete(false);
            mDefaultFilter.setSupportsEdit(false);
        }
    
private org.eclipse.swt.widgets.TabItemcreateTab(LogFilter filter, int index, boolean fillTable)
Creates a new tab in the folderTab item. Must be called from the ui thread.

param
filter The filter associated with the tab.
param
index the index of the tab. if -1, the tab will be added at the end.
param
fillTable If true the table is filled with the current content of the buffer.
return
The TabItem object that was created.

        synchronized (mBuffer) {
            TabItem item = null;
            if (index != -1) {
                item = new TabItem(mFolders, SWT.NONE, index);
            } else {
                item = new TabItem(mFolders, SWT.NONE);
            }
            item.setText(filter.getName());

            // set the control (the parent is the TabFolder item, always)
            Composite top = new Composite(mFolders, SWT.NONE);
            item.setControl(top);

            top.setLayout(new FillLayout());

            // create the ui, first the table
            final Table t = new Table(top, SWT.MULTI | SWT.FULL_SELECTION);

            if (mDisplayFont != null) {
                t.setFont(mDisplayFont);
            }

            // give the ui objects to the filters.
            filter.setWidgets(item, t);

            t.setHeaderVisible(true);
            t.setLinesVisible(false);

            if (mGlobalListener != null) {
            	addTableToFocusListener(t);
            }

            // create a controllistener that will handle the resizing of all the
            // columns (except the last) and of the table itself.
            ControlListener listener = null;
            if (mColumnMode == COLUMN_MODE_AUTO) {
                listener = new ControlListener() {
                    public void controlMoved(ControlEvent e) {
                    }

                    public void controlResized(ControlEvent e) {
                        Rectangle r = t.getClientArea();

                        // get the size of all but the last column
                        int total = t.getColumn(0).getWidth();
                        total += t.getColumn(1).getWidth();
                        total += t.getColumn(2).getWidth();
                        total += t.getColumn(3).getWidth();

                        if (r.width > total) {
                            t.getColumn(4).setWidth(r.width-total);
                        }
                    }
                };

                t.addControlListener(listener);
            }

            // then its column
            TableColumn col = TableHelper.createTableColumn(t, "Time", SWT.LEFT,
                    "00-00 00:00:00", //$NON-NLS-1$
                    PREFS_TIME, mStore);
            if (mColumnMode == COLUMN_MODE_AUTO) {
                col.addControlListener(listener);
            }

            col = TableHelper.createTableColumn(t, "", SWT.CENTER,
                    "D", //$NON-NLS-1$
                    PREFS_LEVEL, mStore);
            if (mColumnMode == COLUMN_MODE_AUTO) {
                col.addControlListener(listener);
            }

            col = TableHelper.createTableColumn(t, "pid", SWT.LEFT,
                    "9999", //$NON-NLS-1$
                    PREFS_PID, mStore);
            if (mColumnMode == COLUMN_MODE_AUTO) {
                col.addControlListener(listener);
            }

            col = TableHelper.createTableColumn(t, "tag", SWT.LEFT,
                    "abcdefgh",  //$NON-NLS-1$
                    PREFS_TAG, mStore);
            if (mColumnMode == COLUMN_MODE_AUTO) {
                col.addControlListener(listener);
            }

            col = TableHelper.createTableColumn(t, "Message", SWT.LEFT,
                    "abcdefghijklmnopqrstuvwxyz0123456789",  //$NON-NLS-1$
                    PREFS_MESSAGE, mStore);
            if (mColumnMode == COLUMN_MODE_AUTO) {
                // instead of listening on resize for the last column, we make
                // it non resizable.
                col.setResizable(false);
            }

            if (fillTable) {
                initFilter(filter);
            }
            return item;
        }
    
public voiddeleteFilter()
Deletes the current filter.

        synchronized (mBuffer) {
            if (mCurrentFilter != null && mCurrentFilter != mDefaultFilter) {
                // remove the filter from the list
                removeFilterFromArray(mCurrentFilter);
                mCurrentFilter.dispose();

                // select the new filter
                mFolders.setSelection(0);
                if (mFilters.length > 0) {
                    mCurrentFilter = mFilters[0];
                } else {
                    mCurrentFilter = mDefaultFilter;
                }

                selectionChanged(mCurrentFilter);

                // update the content of the "other" filter to include what was filtered out
                // by the deleted filter.
                if (mDefaultFilter != null) {
                    initDefaultFilter();
                }

                mFilterStorage.saveFilters(mFilters);
            }
        }
    
public voiddeviceSelected()
Sent when a new device is selected. The new device can be accessed with {@link #getCurrentDevice()}.

        startLogCat(getCurrentDevice());
    
public voideditFilter()
Edits the current filter. The method displays the UI to edit the filter.

        if (mCurrentFilter != null && mCurrentFilter != mDefaultFilter) {
            EditFilterDialog dlg = new EditFilterDialog(mImageLoader,
                    mFolders.getShell(),
                    mCurrentFilter);
            if (dlg.open()) {
                synchronized (mBuffer) {
                    // at this point the filter has been updated.
                    // so we update its content
                    initFilter(mCurrentFilter);

                    // and the content of the "other" filter as well.
                    if (mDefaultFilter != null) {
                        initDefaultFilter();
                    }

                    mFilterStorage.saveFilters(mFilters);
                }
            }
        }
    
private voidemptyTables()

        for (LogFilter f : mFilters) {
            f.getTable().removeAll();
        }

        if (mDefaultFilter != null) {
            mDefaultFilter.getTable().removeAll();
        }
    
private LogFiltergetCurrentFilter()

        int index = mFolders.getSelectionIndex();

        // if mFilters is null or index is invalid, we return the default
        // filter. It doesn't matter if that one is null as well, since we
        // would return null anyway.
        if (index == 0 || mFilters == null) {
            return mDefaultFilter;
        }

        return mFilters[index-1];
    
private voidinitDefaultFilter()
Refill the default filter. Not to be called directly.

see
initFilter()

        mDefaultFilter.clear();

        if (mBufferStart != -1) {
            int max = mBufferEnd;
            if (mBufferEnd < mBufferStart) {
                max += STRING_BUFFER_LENGTH;
            }

            for (int i = mBufferStart; i < max; i++) {
                int realItemIndex = i % STRING_BUFFER_LENGTH;
                LogMessage msg = mBuffer[realItemIndex];

                // first we check that the other filters don't take this message
                boolean filtered = false;
                for (LogFilter f : mFilters) {
                    filtered |= f.accept(msg);
                }

                if (filtered == false) {
                    mDefaultFilter.addMessage(msg, null /* old message */);
                }
            }
        }

        mDefaultFilter.flush();
        mDefaultFilter.resetTempFilteringStatus();
    
private voidinitFilter(LogFilter filter)
Initialize the filter with already existing buffer.

param
filter

        // is it empty
        if (filter.uiReady() == false) {
            return;
        }

        if (filter == mDefaultFilter) {
            initDefaultFilter();
            return;
        }
        
        filter.clear();

        if (mBufferStart != -1) {
            int max = mBufferEnd;
            if (mBufferEnd < mBufferStart) {
                max += STRING_BUFFER_LENGTH;
            }

            for (int i = mBufferStart; i < max; i++) {
                int realItemIndex = i % STRING_BUFFER_LENGTH;

                filter.addMessage(mBuffer[realItemIndex], null /* old message */);
            }
        }

        filter.flush();
        filter.resetTempFilteringStatus();
    
protected voidpostCreation()

        // pass
    
protected voidprocessLogLines(java.lang.String[] lines)
Process new Log lines coming from {@link LogCatOuputReceiver}.

param
lines the new lines

        // WARNING: this will not work if the string contains more line than
        // the buffer holds.

        if (lines.length > STRING_BUFFER_LENGTH) {
            Log.e("LogCat", "Receiving more lines than STRING_BUFFER_LENGTH");
        }
        
        // parse the lines and create LogMessage that are stored in a temporary list
        final ArrayList<LogMessage> newMessages = new ArrayList<LogMessage>();
        
        synchronized (mBuffer) {
            for (String line : lines) {
                // ignore empty lines.
                if (line.length() > 0) {
                    // check for header lines.
                    Matcher matcher = sLogPattern.matcher(line);
                    if (matcher.matches()) {
                        // this is a header line, parse the header and keep it around.
                        mLastMessageInfo = new LogMessageInfo();
    
                        mLastMessageInfo.time = matcher.group(1);
                        mLastMessageInfo.pidString = matcher.group(2);
                        mLastMessageInfo.pid = Integer.valueOf(mLastMessageInfo.pidString);
                        mLastMessageInfo.logLevel = LogLevel.getByLetterString(matcher.group(4));
                        mLastMessageInfo.tag = matcher.group(5).trim();
                    } else {
                        // This is not a header line.
                        // Create a new LogMessage and process it.
                        LogMessage mc = new LogMessage();
    
                        if (mLastMessageInfo == null) {
                            // The first line of output wasn't preceded
                            // by a header line; make something up so
                            // that users of mc.data don't NPE.
                            mLastMessageInfo = new LogMessageInfo();
                            mLastMessageInfo.time = "??-?? ??:??:??.???"; //$NON-NLS1$
                            mLastMessageInfo.pidString = "<unknown>"; //$NON-NLS1$
                            mLastMessageInfo.pid = 0;
                            mLastMessageInfo.logLevel = LogLevel.INFO;
                            mLastMessageInfo.tag = "<unknown>"; //$NON-NLS1$
                        }
    
                        // If someone printed a log message with
                        // embedded '\n' characters, there will
                        // one header line followed by multiple text lines.
                        // Use the last header that we saw.
                        mc.data = mLastMessageInfo;
    
                        // tabs seem to display as only 1 tab so we replace the leading tabs
                        // by 4 spaces.
                        mc.msg = line.replaceAll("\t", "    "); //$NON-NLS-1$ //$NON-NLS-2$
                        
                        // process the new LogMessage.
                        processNewMessage(mc);
                        
                        // store the new LogMessage
                        newMessages.add(mc);
                    }
                }
            }
            
            // if we don't have a pending Runnable that will do the refresh, we ask the Display
            // to run one in the UI thread.
            if (mPendingAsyncRefresh == false) {
                mPendingAsyncRefresh = true;
                
                try {
                    Display display = mFolders.getDisplay();
                    
                    // run in sync because this will update the buffer start/end indices
                    display.asyncExec(new Runnable() {
                        public void run() {
                            asyncRefresh();
                        }
                    });
                } catch (SWTException e) {
                    // display is disposed, we're probably quitting. Let's stop.
                    stopLogCat(false);
                }
            }
        }
    
private voidprocessNewMessage(com.android.ddmuilib.logcat.LogPanel$LogMessage newMessage)
Processes a new Message.

This adds the new message to the buffer, and gives it to the existing filters.

param
newMessage

        // if we are in auto filtering mode, make sure we have
        // a filter for this
        if (mFilterMode == FILTER_AUTO_PID ||
                mFilterMode == FILTER_AUTO_TAG) {
           checkFilter(newMessage.data);
        }

        // compute the index where the message goes.
        // was the buffer empty?
        int messageIndex = -1;
        if (mBufferStart == -1) {
            messageIndex = mBufferStart = 0;
            mBufferEnd = 1;
        } else {
            messageIndex = mBufferEnd;

            // check we aren't overwriting start
            if (mBufferEnd == mBufferStart) {
                mBufferStart = (mBufferStart + 1) % STRING_BUFFER_LENGTH;
            }

            // increment the next usable slot index
            mBufferEnd = (mBufferEnd + 1) % STRING_BUFFER_LENGTH;
        }
        
        LogMessage oldMessage = null;

        // record the message that was there before
        if (mBuffer[messageIndex] != null) {
            oldMessage = mBuffer[messageIndex];
        }

        // then add the new one
        mBuffer[messageIndex] = newMessage;

        // give the new message to every filters.
        boolean filtered = false;
        if (mFilters != null) {
            for (LogFilter f : mFilters) {
                filtered |= f.addMessage(newMessage, oldMessage);
            }
        }
        if (filtered == false && mDefaultFilter != null) {
            mDefaultFilter.addMessage(newMessage, oldMessage);
        }
    
private voidremoveFilterFromArray(LogFilter oldFilter)

        // look for the index
        int index = -1;
        for (int i = 0 ; i < mFilters.length ; i++) {
            if (mFilters[i] == oldFilter) {
                index = i;
                break;
            }
        }

        if (index != -1) {
            LogFilter[] newFilters = new LogFilter[mFilters.length-1];
            System.arraycopy(mFilters, 0, newFilters, 0, index);
            System.arraycopy(mFilters, index + 1, newFilters, index,
                    newFilters.length-index);
            mFilters = newFilters;
        }
    
private voidresetFilters()
Reset the filters, to handle change in device in automatic filter mode

        // if we are in automatic mode, then we need to rmove the current
        // filter.
        if (mFilterMode == FILTER_AUTO_PID || mFilterMode == FILTER_AUTO_TAG) {
            mFilters = null;

            // recreate the filters.
            createFilters();
        }
    
public voidresetUI(boolean inUiThread)

        if (mFilterMode == FILTER_AUTO_PID || mFilterMode == FILTER_AUTO_TAG) {
            if (inUiThread) {
                mFolders.dispose();
                mParent.pack(true);
                createControl(mParent);
            } else {
                Display d = mFolders.getDisplay();

                // run sync as we need to update right now.
                d.syncExec(new Runnable() {
                    public void run() {
                        mFolders.dispose();
                        mParent.pack(true);
                        createControl(mParent);
                    }
                });
            }
        } else  {
            // the ui is static we just empty it.
            if (mFolders.isDisposed() == false) {
                if (inUiThread) {
                    emptyTables();
                } else {
                    Display d = mFolders.getDisplay();

                    // run sync as we need to update right now.
                    d.syncExec(new Runnable() {
                        public void run() {
                            if (mFolders.isDisposed() == false) {
                                emptyTables();
                            }
                        }
                    });
                }
            }
        }
    
public booleansave()
saves the current selection in a text file.

return
false if the saving failed.

        synchronized (mBuffer) {
            FileDialog dlg = new FileDialog(mParent.getShell(), SWT.SAVE);
            String fileName;
    
            dlg.setText("Save log...");
            dlg.setFileName("log.txt");
            String defaultPath = mDefaultLogSave;
            if (defaultPath == null) {
                defaultPath = System.getProperty("user.home"); //$NON-NLS-1$
            }
            dlg.setFilterPath(defaultPath);
            dlg.setFilterNames(new String[] {
                "Text Files (*.txt)"
            });
            dlg.setFilterExtensions(new String[] {
                "*.txt"
            });
    
            fileName = dlg.open();
            if (fileName != null) {
                mDefaultLogSave = dlg.getFilterPath();

                // get the current table and its selection
                Table currentTable = mCurrentFilter.getTable();

                int[] selection = currentTable.getSelectionIndices();

                // we need to sort the items to be sure.
                Arrays.sort(selection);

                // loop on the selection and output the file.
                try {
                    FileWriter writer = new FileWriter(fileName);

                    for (int i : selection) {
                        TableItem item = currentTable.getItem(i);
                        LogMessage msg = (LogMessage)item.getData();
                        String line = msg.toString();
                        writer.write(line);
                        writer.write('\n");
                    }
                    writer.flush();

                } catch (IOException e) {
                    return false;
                }
            }
        }

        return true;
    
public voidselectAll()
Selects all lines.

        Table currentTable = mCurrentFilter.getTable();
        currentTable.selectAll();
    
private voidselectionChanged(LogFilter selectedFilter)
Called when the current filter selection changes.

param
selectedFilter

        if (mLogLevelActions != null) {
            // get the log level
            int level = selectedFilter.getLogLevel();
            for (int i = 0 ; i < mLogLevelActions.length; i++) {
                ICommonAction a = mLogLevelActions[i];
                if (i == level - 2) {
                    a.setChecked(true);
                } else {
                    a.setChecked(false);
                }
            }
        }

        if (mDeleteFilterAction != null) {
            mDeleteFilterAction.setEnabled(selectedFilter.supportsDelete());
        }
        if (mEditFilterAction != null) {
            mEditFilterAction.setEnabled(selectedFilter.supportsEdit());
        }
    
public voidsetActions(com.android.ddmuilib.actions.ICommonAction deleteAction, com.android.ddmuilib.actions.ICommonAction editAction, com.android.ddmuilib.actions.ICommonAction[] logLevelActions)

        mDeleteFilterAction = deleteAction;
        mEditFilterAction = editAction;
        mLogLevelActions = logLevelActions;
    
public voidsetColumnMode(int mode)
Sets the column mode. Must be called before creatUI

param
mode the column mode. Valid values are COLUMN_MOD_MANUAL and COLUMN_MODE_AUTO

        mColumnMode  = mode;
    
public voidsetCurrentFilterLogLevel(int i)
Sets the log level for the current filter, but does not save it.

param
i

        LogFilter filter = getCurrentFilter();

        filter.setLogLevel(i);

        initFilter(filter);
    
public voidsetFocus()
Sets the focus to the proper object.

        mFolders.setFocus();
    
public voidsetFont(org.eclipse.swt.graphics.Font font)
Sets the display font.

param
font The display font.

        mDisplayFont = font;

        if (mFilters != null) {
            for (LogFilter f : mFilters) {
                Table table = f.getTable();
                if (table != null) {
                    table.setFont(font);
                }
            }
        }

        if (mDefaultFilter != null) {
            Table table = mDefaultFilter.getTable();
            if (table != null) {
                table.setFont(font);
            }
        }
    
public voidsetTableFocusListener(com.android.ddmuilib.ITableFocusListener listener)
Sets a TableFocusListener which will be notified when one of the tables gets or loses focus.

param
listener

        // record the global listener, to make sure table created after
        // this call will still be setup.
        mGlobalListener = listener;

        // now we setup the existing filters
        for (LogFilter filter : mFilters) {
            Table table = filter.getTable();

            addTableToFocusListener(table);
        }

        // and the default one
        if (mDefaultFilter != null) {
            addTableToFocusListener(mDefaultFilter.getTable());
        }
    
public voidstartLogCat(com.android.ddmlib.Device device)
Starts a new logcat and set mCurrentLogCat as the current receiver.

param
device the device to connect logcat to.

        if (device == mCurrentLoggedDevice) {
            return;
        }

        // if we have a logcat already running
        if (mCurrentLoggedDevice != null) {
            stopLogCat(false);
            mCurrentLoggedDevice = null;
        }
        
        resetUI(false);

        if (device != null) {
            // create a new output receiver
            mCurrentLogCat = new LogCatOuputReceiver();

            // start the logcat in a different thread
            new Thread("Logcat")  { //$NON-NLS-1$
                @Override
                public void run() {

                    while (device.isOnline() == false &&
                            mCurrentLogCat != null &&
                            mCurrentLogCat.isCancelled == false) {
                        try {
                            sleep(2000);
                        } catch (InterruptedException e) {
                            return;
                        }
                    }

                    if (mCurrentLogCat == null || mCurrentLogCat.isCancelled) {
                        // logcat was stopped/cancelled before the device became ready.
                        return;
                    }

                    try {
                        mCurrentLoggedDevice = device;
                        device.executeShellCommand("logcat -v long", mCurrentLogCat); //$NON-NLS-1$
                    } catch (Exception e) {
                        Log.e("Logcat", e);
                    } finally {
                        // at this point the command is terminated.
                        mCurrentLogCat = null;
                        mCurrentLoggedDevice = null;
                    }
                }
            }.start();
        }
    
public voidstopLogCat(boolean inUiThread)
Stop the current logcat

        if (mCurrentLogCat != null) {
            mCurrentLogCat.isCancelled = true;

            // when the thread finishes, no one will reference that object
            // and it'll be destroyed
            mCurrentLogCat = null;

            // reset the content buffer
            for (int i = 0 ; i < STRING_BUFFER_LENGTH; i++) {
                mBuffer[i] = null;
            }

            // because it's a circular buffer, it's hard to know if
            // the array is empty with both start/end at 0 or if it's full
            // with both start/end at 0 as well. So to mean empty, we use -1
            mBufferStart = -1;
            mBufferEnd = -1;

            resetFilters();
            resetUI(inUiThread);
        }
    
protected voidupdateColumns(org.eclipse.swt.widgets.Table table)

        if (table != null) {
            int index = 0;
            TableColumn col;

            col = table.getColumn(index++);
            col.setWidth(mStore.getInt(PREFS_TIME));

            col = table.getColumn(index++);
            col.setWidth(mStore.getInt(PREFS_LEVEL));

            col = table.getColumn(index++);
            col.setWidth(mStore.getInt(PREFS_PID));

            col = table.getColumn(index++);
            col.setWidth(mStore.getInt(PREFS_TAG));

            col = table.getColumn(index++);
            col.setWidth(mStore.getInt(PREFS_MESSAGE));
        }
    
protected voidupdateFilteringWith(java.lang.String text)

        synchronized (mBuffer) {
            // reset the temp filtering for all the filters
            for (LogFilter f : mFilters) {
                f.resetTempFiltering();
            }
            if (mDefaultFilter != null) {
                mDefaultFilter.resetTempFiltering();
            }
    
            // now we need to figure out the new temp filtering
            // split each word
            String[] segments = text.split(" "); //$NON-NLS-1$
    
            ArrayList<String> keywords = new ArrayList<String>(segments.length);
    
            // loop and look for temp id/tag
            int tempPid = -1;
            String tempTag = null;
            for (int i = 0 ; i < segments.length; i++) {
                String s = segments[i];
                if (tempPid == -1 && s.startsWith("pid:")) { //$NON-NLS-1$
                    // get the pid
                    String[] seg = s.split(":"); //$NON-NLS-1$
                    if (seg.length == 2) {
                        if (seg[1].matches("^[0-9]*$")) { //$NON-NLS-1$
                            tempPid = Integer.valueOf(seg[1]);
                        }
                    }
                } else if (tempTag == null && s.startsWith("tag:")) { //$NON-NLS-1$
                    String seg[] = segments[i].split(":"); //$NON-NLS-1$
                    if (seg.length == 2) {
                        tempTag = seg[1];
                    }
                } else {
                    keywords.add(s);
                }
            }
    
            // set the temp filtering in the filters
            if (tempPid != -1 || tempTag != null || keywords.size() > 0) {
                String[] keywordsArray = keywords.toArray(
                        new String[keywords.size()]);
    
                for (LogFilter f : mFilters) {
                    if (tempPid != -1) {
                        f.setTempPidFiltering(tempPid);
                    }
                    if (tempTag != null) {
                        f.setTempTagFiltering(tempTag);
                    }
                    f.setTempKeywordFiltering(keywordsArray);
                }
    
                if (mDefaultFilter != null) {
                    if (tempPid != -1) {
                        mDefaultFilter.setTempPidFiltering(tempPid);
                    }
                    if (tempTag != null) {
                        mDefaultFilter.setTempTagFiltering(tempTag);
                    }
                    mDefaultFilter.setTempKeywordFiltering(keywordsArray);
    
                }
            }
    
            initFilter(mCurrentFilter);
        }