aboutsummaryrefslogtreecommitdiffstats
path: root/ddms
diff options
context:
space:
mode:
authorSiva Velusamy <vsiva@google.com>2012-04-30 15:12:15 -0700
committerSiva Velusamy <vsiva@google.com>2012-05-03 13:38:44 -0700
commit231d5ea266e58657282865b1166e329fd333dcd1 (patch)
tree84c78df23b543cb7fc959bcabb7b041eb441d7c7 /ddms
parent42ba62270d9102024f4400923eba4ce46a951163 (diff)
downloadsdk-231d5ea266e58657282865b1166e329fd333dcd1.zip
sdk-231d5ea266e58657282865b1166e329fd333dcd1.tar.gz
sdk-231d5ea266e58657282865b1166e329fd333dcd1.tar.bz2
logcat: Remove JFace TableViewer and use SWT Table directly
This patch fixes a bunch of outstanding issues related to scrolling in the presence of a full buffer. Currently, the logbuffer is provided as the input model to the TableViewer, and ViewerFilter's are used to filter the data. This patch removes the JFace toolkit and directly works on the SWT Table. When log messages arrive, rather than refreshing the entire table, we can now just delete the TableItems corresponding to the logs that were pushed out, and add new TableItems for the incoming logs. At steady state, this implementation performs far less work than the previous implementation. However, during startup, this implementation will perform more work since it does not use the SWT.VIRTUAL bit (as all TableItems are created anyway). Also, zebra striping has been removed to avoid appearance of flicker when scroll lock is on. Auto scroll lock behavior has been removed, and scroll lock button behaves exactly like the scroll lock button in an Eclipse console. Change-Id: Ic14487f7ad41338a581aed0ba2d85d292a584950
Diffstat (limited to 'ddms')
-rw-r--r--ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/ILogCatBufferChangeListener.java (renamed from ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/ILogCatMessageEventListener.java)12
-rw-r--r--ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatFilterContentProvider.java6
-rw-r--r--ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatFilterSettingsDialog.java2
-rw-r--r--ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatMessageContentProvider.java43
-rw-r--r--ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatMessageLabelProvider.java144
-rw-r--r--ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatMessageList.java53
-rw-r--r--ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatPanel.java606
-rw-r--r--ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatReceiver.java27
-rw-r--r--ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatViewerFilter.java46
9 files changed, 387 insertions, 552 deletions
diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/ILogCatMessageEventListener.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/ILogCatBufferChangeListener.java
index 2caf50d..1a547c7 100644
--- a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/ILogCatMessageEventListener.java
+++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/ILogCatBufferChangeListener.java
@@ -19,11 +19,13 @@ package com.android.ddmuilib.logcat;
import java.util.List;
/**
- * Listeners interested in log cat messages should implement this interface.
+ * Listeners interested in changes in the logcat buffer should implement this interface.
*/
-public interface ILogCatMessageEventListener {
- /** Called on reception of logcat messages.
- * @param receivedMessages list of messages received
+public interface ILogCatBufferChangeListener {
+ /**
+ * Called when the logcat buffer changes.
+ * @param addedMessages list of messages that were added to the logcat buffer
+ * @param deletedMessages list of messages that were removed from the logcat buffer
*/
- void messageReceived(List<LogCatMessage> receivedMessages);
+ void bufferChanged(List<LogCatMessage> addedMessages, List<LogCatMessage> deletedMessages);
}
diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatFilterContentProvider.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatFilterContentProvider.java
index 164f484..68c08d4 100644
--- a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatFilterContentProvider.java
+++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatFilterContentProvider.java
@@ -39,10 +39,6 @@ public final class LogCatFilterContentProvider implements IStructuredContentProv
*/
@Override
public Object[] getElements(Object model) {
- if (model instanceof List<?>) {
- return ((List<?>) model).toArray();
- }
- return null;
+ return ((List<?>) model).toArray();
}
-
}
diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatFilterSettingsDialog.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatFilterSettingsDialog.java
index f68ee05..39b3fa9 100644
--- a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatFilterSettingsDialog.java
+++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatFilterSettingsDialog.java
@@ -165,7 +165,7 @@ public final class LogCatFilterSettingsDialog extends TitleAreaDialog {
* on the dialog is valid or not. If it is not valid, the message
* field stores the reason why it isn't.
*/
- private final class DialogStatus {
+ private static final class DialogStatus {
final boolean valid;
final String message;
diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatMessageContentProvider.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatMessageContentProvider.java
deleted file mode 100644
index bd7b520..0000000
--- a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatMessageContentProvider.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ddmuilib.logcat;
-
-import org.eclipse.jface.viewers.IStructuredContentProvider;
-import org.eclipse.jface.viewers.Viewer;
-
-/**
- * A JFace content provider for the LogCat log messages, used in the {@link LogCatPanel}.
- */
-public final class LogCatMessageContentProvider implements IStructuredContentProvider {
- @Override
- public void dispose() {
- }
-
- @Override
- public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
- }
-
- @Override
- public Object[] getElements(Object model) {
- if (model instanceof LogCatMessageList) {
- Object[] e = ((LogCatMessageList) model).toArray();
- return e;
- }
-
- return null;
- }
-}
diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatMessageLabelProvider.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatMessageLabelProvider.java
deleted file mode 100644
index 1d83a9c..0000000
--- a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatMessageLabelProvider.java
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ddmuilib.logcat;
-
-import com.android.ddmlib.Log.LogLevel;
-
-import org.eclipse.jface.viewers.ColumnLabelProvider;
-import org.eclipse.jface.viewers.ViewerCell;
-import org.eclipse.swt.graphics.Color;
-import org.eclipse.swt.graphics.Font;
-import org.eclipse.swt.graphics.Point;
-
-/**
- * A JFace Column label provider for the LogCat log messages. It expects elements of type
- * {@link LogCatMessage}.
- */
-public final class LogCatMessageLabelProvider extends ColumnLabelProvider {
- private static final int INDEX_LOGLEVEL = 0;
- private static final int INDEX_LOGTIME = 1;
- private static final int INDEX_PID = 2;
- private static final int INDEX_APPNAME = 3;
- private static final int INDEX_TAG = 4;
- private static final int INDEX_TEXT = 5;
-
- /* Default Colors for different log levels. */
- private static final Color INFO_MSG_COLOR = new Color(null, 0, 127, 0);
- private static final Color DEBUG_MSG_COLOR = new Color(null, 0, 0, 127);
- private static final Color ERROR_MSG_COLOR = new Color(null, 255, 0, 0);
- private static final Color WARN_MSG_COLOR = new Color(null, 255, 127, 0);
- private static final Color VERBOSE_MSG_COLOR = new Color(null, 0, 0, 0);
-
- /** Amount of pixels to shift the tooltip by. */
- private static final Point LOGCAT_TOOLTIP_SHIFT = new Point(10, 10);
-
- private Font mLogFont;
- private int mWrapWidth = 100;
-
- /**
- * Construct a column label provider for the logcat table.
- * @param font default font to use
- */
- public LogCatMessageLabelProvider(Font font) {
- mLogFont = font;
- }
-
- private String getCellText(LogCatMessage m, int columnIndex) {
- switch (columnIndex) {
- case INDEX_LOGLEVEL:
- return Character.toString(m.getLogLevel().getPriorityLetter());
- case INDEX_LOGTIME:
- return m.getTime();
- case INDEX_PID:
- return m.getPid();
- case INDEX_APPNAME:
- return m.getAppName();
- case INDEX_TAG:
- return m.getTag();
- case INDEX_TEXT:
- return m.getMessage();
- default:
- return "";
- }
- }
-
- @Override
- public void update(ViewerCell cell) {
- Object element = cell.getElement();
- if (!(element instanceof LogCatMessage)) {
- return;
- }
- LogCatMessage m = (LogCatMessage) element;
-
- String text = getCellText(m, cell.getColumnIndex());
- cell.setText(text);
- cell.setFont(mLogFont);
- cell.setForeground(getForegroundColor(m));
- }
-
- private Color getForegroundColor(LogCatMessage m) {
- LogLevel l = m.getLogLevel();
-
- if (l.equals(LogLevel.VERBOSE)) {
- return VERBOSE_MSG_COLOR;
- } else if (l.equals(LogLevel.INFO)) {
- return INFO_MSG_COLOR;
- } else if (l.equals(LogLevel.DEBUG)) {
- return DEBUG_MSG_COLOR;
- } else if (l.equals(LogLevel.ERROR)) {
- return ERROR_MSG_COLOR;
- } else if (l.equals(LogLevel.WARN)) {
- return WARN_MSG_COLOR;
- }
-
- return null;
- }
-
- public void setFont(Font preferredFont) {
- if (mLogFont != null) {
- mLogFont.dispose();
- }
-
- mLogFont = preferredFont;
- }
-
- public void setMinimumLengthForToolTips(int widthInChars) {
- mWrapWidth = widthInChars;
- }
-
- /**
- * Obtain the tool tip to show for a particular logcat message.
- * We display a tool tip only for messages longer than the width set by
- * {@link #setMinimumLengthForToolTips(int)}.
- */
- @Override
- public String getToolTipText(Object element) {
- String text = element.toString();
- if (text.length() > mWrapWidth) {
- return text;
- } else {
- return null;
- }
- }
-
- @Override
- public Point getToolTipShift(Object object) {
- // The only reason we override this method is that the default shift amounts
- // don't seem to work on OS X Lion.
- return LOGCAT_TOOLTIP_SHIFT;
- }
-}
diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatMessageList.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatMessageList.java
index 0d0e3c2..080dbc1 100644
--- a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatMessageList.java
+++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatMessageList.java
@@ -16,6 +16,8 @@
package com.android.ddmuilib.logcat;
+import java.util.ArrayList;
+import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
@@ -33,7 +35,6 @@ public final class LogCatMessageList {
private int mFifoSize;
private BlockingQueue<LogCatMessage> mQ;
- private LogCatMessage[] mQArray;
/**
* Construct an empty message list.
@@ -43,7 +44,6 @@ public final class LogCatMessageList {
mFifoSize = maxMessages;
mQ = new ArrayBlockingQueue<LogCatMessage>(mFifoSize);
- mQArray = new LogCatMessage[mFifoSize];
}
/**
@@ -64,8 +64,6 @@ public final class LogCatMessageList {
mQ.offer(curMessages[i]);
}
}
-
- mQArray = new LogCatMessage[mFifoSize];
}
/**
@@ -73,17 +71,30 @@ public final class LogCatMessageList {
* message will be popped off of it.
* @param m log to be inserted
*/
- public synchronized void appendMessage(final LogCatMessage m) {
- if (mQ.remainingCapacity() == 0) {
- /* make space by removing the first entry */
- mQ.poll();
+ public synchronized void appendMessages(final List<LogCatMessage> messages) {
+ ensureSpace(messages.size());
+ for (LogCatMessage m: messages) {
+ mQ.offer(m);
}
- mQ.offer(m);
}
/**
- * Returns the number of additional elements that this queue can
- * ideally (in the absence of memory or resource constraints)
+ * Ensure that there is sufficient space for given number of messages.
+ * @return list of messages that were deleted to create additional space.
+ */
+ public synchronized List<LogCatMessage> ensureSpace(int messageCount) {
+ List<LogCatMessage> l = new ArrayList<LogCatMessage>(messageCount);
+
+ while (mQ.remainingCapacity() < messageCount) {
+ l.add(mQ.poll());
+ }
+
+ return l;
+ }
+
+ /**
+ * Returns the number of additional elements that this queue can
+ * ideally (in the absence of memory or resource constraints)
* accept without blocking.
* @return the remaining capacity
*/
@@ -91,25 +102,13 @@ public final class LogCatMessageList {
return mQ.remainingCapacity();
}
- /**
- * Clear all messages in the list.
- */
+ /** Clear all messages in the list. */
public synchronized void clear() {
mQ.clear();
}
- /**
- * Obtain all the messages currently present in the list.
- * @return array containing all the log messages
- */
- public Object[] toArray() {
- if (mQ.size() == mFifoSize) {
- /*
- * Once the queue is full, it stays full until the user explicitly clears
- * all the logs. Optimize for this case by not reallocating the array.
- */
- return mQ.toArray(mQArray);
- }
- return mQ.toArray();
+ /** Obtain a copy of the message list. */
+ public synchronized List<LogCatMessage> getAllMessages() {
+ return new ArrayList<LogCatMessage>(mQ);
}
}
diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatPanel.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatPanel.java
index 7aa0328..45f0c69 100644
--- a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatPanel.java
+++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatPanel.java
@@ -30,12 +30,7 @@ import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.preference.PreferenceConverter;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
-import org.eclipse.jface.viewers.ColumnViewer;
-import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
import org.eclipse.jface.viewers.TableViewer;
-import org.eclipse.jface.viewers.TableViewerColumn;
-import org.eclipse.jface.viewers.ViewerCell;
-import org.eclipse.jface.window.ToolTip;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
@@ -50,6 +45,7 @@ import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.GC;
@@ -63,7 +59,6 @@ import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
-import org.eclipse.swt.widgets.ScrollBar;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
@@ -83,7 +78,7 @@ import java.util.List;
* LogCatPanel displays a table listing the logcat messages.
*/
public final class LogCatPanel extends SelectionDependentPanel
- implements ILogCatMessageEventListener {
+ implements ILogCatBufferChangeListener {
/** Preference key to use for storing list of logcat filters. */
public static final String LOGCAT_FILTERS_LIST = "logcat.view.filters.list";
@@ -121,20 +116,20 @@ public final class LogCatPanel extends SelectionDependentPanel
private static final String IMAGE_SAVE_LOG_TO_FILE = "save.png"; //$NON-NLS-1$
private static final String IMAGE_CLEAR_LOG = "clear.png"; //$NON-NLS-1$
private static final String IMAGE_DISPLAY_FILTERS = "displayfilters.png"; //$NON-NLS-1$
- private static final String IMAGE_PAUSE_LOGCAT = "pause_logcat.png"; //$NON-NLS-1$
+ private static final String IMAGE_SCROLL_LOCK = "pause_logcat.png"; //$NON-NLS-1$
private static final int[] WEIGHTS_SHOW_FILTERS = new int[] {15, 85};
private static final int[] WEIGHTS_LOGCAT_ONLY = new int[] {0, 100};
+ /** Index of the default filter in the saved filters column. */
+ private static final int DEFAULT_FILTER_INDEX = 0;
+
private LogCatReceiver mReceiver;
private IPreferenceStore mPrefStore;
private List<LogCatFilter> mLogCatFilters;
private int mCurrentSelectedFilterIndex;
- private int mRemovedEntriesCount = 0;
- private int mPreviousRemainingCapacity = 0;
-
private ToolItem mNewFilterToolItem;
private ToolItem mDeleteFilterToolItem;
private ToolItem mEditFilterToolItem;
@@ -143,28 +138,40 @@ public final class LogCatPanel extends SelectionDependentPanel
private Combo mLiveFilterLevelCombo;
private Text mLiveFilterText;
- private TableViewer mViewer;
+ private List<LogCatFilter> mCurrentFilters = Collections.emptyList();
+
+ private Table mTable;
private boolean mShouldScrollToLatestLog = true;
- private ToolItem mPauseLogcatCheckBox;
- private boolean mLastItemPainted = false;
+ private ToolItem mScrollLockCheckBox;
private String mLogFileExportFolder;
- private LogCatMessageLabelProvider mLogCatMessageLabelProvider;
+
+ private Font mFont;
+ private int mWrapWidthInChars;
private SashForm mSash;
+ // messages added since last refresh, synchronized on mLogBuffer
+ private List<LogCatMessage> mLogBuffer;
+
+ // # of messages deleted since last refresh, synchronized on mLogBuffer
+ private int mDeletedLogCount;
+
/**
* Construct a logcat panel.
* @param prefStore preference store where UI preferences will be saved
*/
public LogCatPanel(IPreferenceStore prefStore) {
mPrefStore = prefStore;
+ mLogBuffer = new ArrayList<LogCatMessage>(LogCatMessageList.MAX_MESSAGES_DEFAULT);
initializeFilters();
setupDefaultPreferences();
initializePreferenceUpdateListeners();
+
+ mFont = getFontFromPrefStore();
}
private void initializeFilters() {
@@ -196,15 +203,24 @@ public final class LogCatPanel extends SelectionDependentPanel
@Override
public void propertyChange(PropertyChangeEvent event) {
String changedProperty = event.getProperty();
-
if (changedProperty.equals(LogCatPanel.LOGCAT_VIEW_FONT_PREFKEY)) {
- mLogCatMessageLabelProvider.setFont(getFontFromPrefStore());
- refreshLogCatTable();
- } else if (changedProperty.equals(
- LogCatMessageList.MAX_MESSAGES_PREFKEY)) {
+ if (mFont != null) {
+ mFont.dispose();
+ }
+ mFont = getFontFromPrefStore();
+ recomputeWrapWidth();
+ Display.getDefault().syncExec(new Runnable() {
+ @Override
+ public void run() {
+ for (TableItem it: mTable.getItems()) {
+ it.setFont(mFont);
+ }
+ }
+ });
+ } else if (changedProperty.equals(LogCatMessageList.MAX_MESSAGES_PREFKEY)) {
mReceiver.resizeFifo(mPrefStore.getInt(
LogCatMessageList.MAX_MESSAGES_PREFKEY));
- refreshLogCatTable();
+ reloadLogBuffer();
}
}
});
@@ -246,7 +262,7 @@ public final class LogCatPanel extends SelectionDependentPanel
mReceiver = LogCatReceiverFactory.INSTANCE.newReceiver(device, mPrefStore);
mReceiver.addMessageReceivedEventListener(this);
- mViewer.setInput(mReceiver.getMessages());
+ reloadLogBuffer();
// Always scroll to last line whenever the selected device changes.
// Run this in a separate async thread to give the table some time to update after the
@@ -430,7 +446,7 @@ public final class LogCatPanel extends SelectionDependentPanel
* @param appName application name to filter by
*/
public void selectTransientAppFilter(String appName) {
- assert mViewer.getTable().getDisplay().getThread() == Thread.currentThread();
+ assert mTable.getDisplay().getThread() == Thread.currentThread();
LogCatFilter f = findTransientAppFilter(appName);
if (f == null) {
@@ -501,11 +517,7 @@ public final class LogCatPanel extends SelectionDependentPanel
createLogcatViewTable(c);
}
- /**
- * Create the search bar at the top of the logcat messages table.
- * FIXME: Currently, this feature is incomplete: The UI elements are created, but they
- * are all set to disabled state.
- */
+ /** Create the search bar at the top of the logcat messages table. */
private void createLiveFilters(Composite parent) {
Composite c = new Composite(parent, SWT.NONE);
c.setLayout(new GridLayout(3, false));
@@ -557,8 +569,6 @@ public final class LogCatPanel extends SelectionDependentPanel
mReceiver.clearMessages();
refreshLogCatTable();
- mRemovedEntriesCount = 0;
-
// the filters view is not cleared unless the filters are re-applied.
updateAppliedFilters();
}
@@ -580,17 +590,17 @@ public final class LogCatPanel extends SelectionDependentPanel
}
});
- mPauseLogcatCheckBox = new ToolItem(toolBar, SWT.CHECK);
- mPauseLogcatCheckBox.setImage(
- ImageLoader.getDdmUiLibLoader().loadImage(IMAGE_PAUSE_LOGCAT,
+ mScrollLockCheckBox = new ToolItem(toolBar, SWT.CHECK);
+ mScrollLockCheckBox.setImage(
+ ImageLoader.getDdmUiLibLoader().loadImage(IMAGE_SCROLL_LOCK,
toolBar.getDisplay()));
- mPauseLogcatCheckBox.setSelection(false);
- mPauseLogcatCheckBox.setToolTipText("Pause receiving new logcat messages.");
- mPauseLogcatCheckBox.addSelectionListener(new SelectionAdapter() {
+ mScrollLockCheckBox.setSelection(false);
+ mScrollLockCheckBox.setToolTipText("Scroll Lock");
+ mScrollLockCheckBox.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent event) {
- boolean pauseLogcat = mPauseLogcatCheckBox.getSelection();
- setScrollToLatestLog(!pauseLogcat, false);
+ boolean scrollLock = mScrollLockCheckBox.getSelection();
+ setScrollToLatestLog(!scrollLock);
}
});
}
@@ -620,13 +630,13 @@ public final class LogCatPanel extends SelectionDependentPanel
Thread t = new Thread(new Runnable() {
@Override
public void run() {
+ BufferedWriter w = null;
try {
- BufferedWriter w = new BufferedWriter(new FileWriter(fName));
+ w = new BufferedWriter(new FileWriter(fName));
for (LogCatMessage m : selectedMessages) {
w.append(m.toString());
w.newLine();
}
- w.close();
} catch (final IOException e) {
Display.getDefault().asyncExec(new Runnable() {
@Override
@@ -637,6 +647,14 @@ public final class LogCatPanel extends SelectionDependentPanel
+ e.getMessage());
}
});
+ } finally {
+ if (w != null) {
+ try {
+ w.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
}
}
});
@@ -675,44 +693,25 @@ public final class LogCatPanel extends SelectionDependentPanel
}
private List<LogCatMessage> getSelectedLogCatMessages() {
- Table table = mViewer.getTable();
- int[] indices = table.getSelectionIndices();
+ int[] indices = mTable.getSelectionIndices();
Arrays.sort(indices); /* Table.getSelectionIndices() does not specify an order */
- // Get items from the table's input as opposed to getting each table item's data.
- // Retrieving table item's data can return NULL in case of a virtual table if the item
- // has not been displayed yet.
- Object input = mViewer.getInput();
- if (!(input instanceof LogCatMessageList)) {
- return Collections.emptyList();
- }
-
- List<LogCatMessage> filteredItems = applyCurrentFilters((LogCatMessageList) input);
List<LogCatMessage> selectedMessages = new ArrayList<LogCatMessage>(indices.length);
for (int i : indices) {
- // consider removed logcat message entries
- i -= mRemovedEntriesCount;
- if (i >= 0 && i < filteredItems.size()) {
- LogCatMessage m = filteredItems.get(i);
- selectedMessages.add(m);
+ Object data = mTable.getItem(i).getData();
+ if (data instanceof LogCatMessage) {
+ selectedMessages.add((LogCatMessage) data);
}
}
return selectedMessages;
}
- private List<LogCatMessage> applyCurrentFilters(LogCatMessageList msgList) {
- Object[] items = msgList.toArray();
- List<LogCatMessage> filteredItems = new ArrayList<LogCatMessage>(items.length);
- List<LogCatViewerFilter> filters = getFiltersToApply();
-
- for (Object item : items) {
- if (!(item instanceof LogCatMessage)) {
- continue;
- }
+ private List<LogCatMessage> applyCurrentFilters(List<LogCatMessage> msgList) {
+ List<LogCatMessage> filteredItems = new ArrayList<LogCatMessage>(msgList.size());
- LogCatMessage msg = (LogCatMessage) item;
- if (!isMessageFiltered(msg, filters)) {
+ for (LogCatMessage msg: msgList) {
+ if (isMessageAccepted(msg, mCurrentFilters)) {
filteredItems.add(msg);
}
}
@@ -720,33 +719,30 @@ public final class LogCatPanel extends SelectionDependentPanel
return filteredItems;
}
- private boolean isMessageFiltered(LogCatMessage msg, List<LogCatViewerFilter> filters) {
- for (LogCatViewerFilter f : filters) {
- if (!f.select(null, null, msg)) {
- // message does not make it through this filter
- return true;
+ private boolean isMessageAccepted(LogCatMessage msg, List<LogCatFilter> filters) {
+ for (LogCatFilter f : filters) {
+ if (!f.matches(msg)) {
+ // not accepted by this filter
+ return false;
}
}
- return false;
+ // accepted by all filters
+ return true;
}
private void createLogcatViewTable(Composite parent) {
- // The SWT.VIRTUAL bit causes the table to be rendered faster. However it makes all rows
- // to be of the same height, thereby clipping any rows with multiple lines of text.
- // In such a case, users can view the full text by hovering over the item and looking at
- // the tooltip.
- final Table table = new Table(parent, SWT.FULL_SELECTION | SWT.MULTI | SWT.VIRTUAL);
- mViewer = new TableViewer(table);
+ mTable = new Table(parent, SWT.FULL_SELECTION | SWT.MULTI);
- table.setLayoutData(new GridData(GridData.FILL_BOTH));
- table.getHorizontalBar().setVisible(true);
+ mTable.setLayoutData(new GridData(GridData.FILL_BOTH));
+ mTable.getHorizontalBar().setVisible(true);
/** Columns to show in the table. */
String[] properties = {
"Level",
"Time",
"PID",
+ "TID",
"Application",
"Tag",
"Text",
@@ -758,32 +754,31 @@ public final class LogCatPanel extends SelectionDependentPanel
" ",
" 00-00 00:00:00.0000 ",
" 0000",
+ " 0000",
" com.android.launcher",
" SampleTagText",
" Log Message field should be pretty long by default. As long as possible for correct display on Mac.",
};
- mLogCatMessageLabelProvider = new LogCatMessageLabelProvider(getFontFromPrefStore());
for (int i = 0; i < properties.length; i++) {
- TableColumn tc = TableHelper.createTableColumn(mViewer.getTable(),
+ TableHelper.createTableColumn(mTable,
properties[i], /* Column title */
SWT.LEFT, /* Column Style */
sampleText[i], /* String to compute default col width */
getColPreferenceKey(properties[i]), /* Preference Store key for this column */
mPrefStore);
- TableViewerColumn tvc = new TableViewerColumn(mViewer, tc);
- tvc.setLabelProvider(mLogCatMessageLabelProvider);
}
- mViewer.getTable().setLinesVisible(true); /* zebra stripe the table */
- mViewer.getTable().setHeaderVisible(true);
- mViewer.setContentProvider(new LogCatMessageContentProvider());
- WrappingToolTipSupport.enableFor(mViewer, ToolTip.NO_RECREATE);
+ // don't zebra stripe the table: When the buffer is full, and scroll lock is on, having
+ // zebra striping means that the background could keep changing depending on the number
+ // of new messages added to the bottom of the log.
+ mTable.setLinesVisible(false);
+ mTable.setHeaderVisible(true);
// Set the row height to be sufficient enough to display the current font.
// This is not strictly necessary, except that on WinXP, the rows showed up clipped. So
// we explicitly set it to be sure.
- mViewer.getTable().addListener(SWT.MeasureItem, new Listener() {
+ mTable.addListener(SWT.MeasureItem, new Listener() {
@Override
public void handleEvent(Event event) {
event.height = event.gc.getFontMetrics().getHeight();
@@ -791,143 +786,45 @@ public final class LogCatPanel extends SelectionDependentPanel
});
// Update the label provider whenever the text column's width changes
- TableColumn textColumn = mViewer.getTable().getColumn(properties.length - 1);
+ TableColumn textColumn = mTable.getColumn(properties.length - 1);
textColumn.addControlListener(new ControlAdapter() {
@Override
public void controlResized(ControlEvent event) {
- TableColumn tc = (TableColumn) event.getSource();
- int width = tc.getWidth();
- GC gc = new GC(tc.getParent());
- int avgCharWidth = gc.getFontMetrics().getAverageCharWidth();
- gc.dispose();
-
- if (mLogCatMessageLabelProvider != null) {
- mLogCatMessageLabelProvider.setMinimumLengthForToolTips(width/avgCharWidth);
- }
+ recomputeWrapWidth();
}
});
- setupAutoScrollLockBehavior();
initDoubleClickListener();
+ recomputeWrapWidth();
}
- /**
- * Setup to automatically enable or disable scroll lock. From a user's perspective,
- * the logcat window will: <ul>
- * <li> Automatically scroll and reveal new entries if the scrollbar is at the bottom. </li>
- * <li> Not scroll even when new messages are received if the scrollbar is not at the bottom.
- * </li>
- * </ul>
- * This requires that we are able to detect where the scrollbar is and what direction
- * it is moving. Unfortunately, that proves to be very platform dependent. Here's the behavior
- * of the scroll events on different platforms: <ul>
- * <li> On Windows, scroll bar events specify which direction the scrollbar is moving, but
- * it is not possible to determine if the scrollbar is right at the end. </li>
- * <li> On Mac/Cocoa, scroll bar events do not specify the direction of movement (it is always
- * set to SWT.DRAG), and it is not possible to identify where the scrollbar is since
- * small movements of the scrollbar are not reflected in sb.getSelection(). </li>
- * <li> On Linux/gtk, we don't get the direction, but we can accurately locate the
- * scrollbar location using getSelection(), getThumb() and getMaximum().
- * </ul>
- */
- private void setupAutoScrollLockBehavior() {
- if (DdmConstants.CURRENT_PLATFORM == DdmConstants.PLATFORM_WINDOWS) {
- // On Windows, it is not possible to detect whether the scrollbar is at the
- // bottom using the values of ScrollBar.getThumb, getSelection and getMaximum.
- // Instead we resort to the following workaround: attach to the paint listener
- // and see if the last item has been painted since the previous scroll event.
- // If the last item has been painted, then we assume that we are at the bottom.
- mViewer.getTable().addListener(SWT.PaintItem, new Listener() {
- @Override
- public void handleEvent(Event event) {
- TableItem item = (TableItem) event.item;
- TableItem[] items = mViewer.getTable().getItems();
- if (items.length > 0 && items[items.length - 1] == item) {
- mLastItemPainted = true;
- }
- }
- });
- mViewer.getTable().getVerticalBar().addSelectionListener(new SelectionAdapter() {
- @Override
- public void widgetSelected(SelectionEvent event) {
- boolean scrollToLast;
- if (event.detail == SWT.ARROW_UP || event.detail == SWT.PAGE_UP
- || event.detail == SWT.HOME) {
- // if we know that we are moving up, then do not scroll down
- scrollToLast = false;
- } else {
- // otherwise, enable scrollToLast only if the last item was displayed
- scrollToLast = mLastItemPainted;
- }
-
- setScrollToLatestLog(scrollToLast, true);
- mLastItemPainted = false;
- }
- });
- } else if (DdmConstants.CURRENT_PLATFORM == DdmConstants.PLATFORM_LINUX) {
- // On Linux/gtk, we do not get any details regarding the scroll event (up/down/etc).
- // So we completely rely on whether the scrollbar is at the bottom or not.
- mViewer.getTable().getVerticalBar().addSelectionListener(new SelectionAdapter() {
- @Override
- public void widgetSelected(SelectionEvent event) {
- ScrollBar sb = (ScrollBar) event.getSource();
- boolean scrollToLast = sb.getSelection() + sb.getThumb() == sb.getMaximum();
- setScrollToLatestLog(scrollToLast, true);
- }
- });
- } else {
- // On Mac, we do not get any details regarding the (trackball) scroll event,
- // nor can we rely on getSelection() changing for small movements. As a result, we
- // do not setup any auto scroll lock behavior. Mac users have to manually pause and
- // unpause if they are looking at a particular item in a high volume stream of events.
+ public void recomputeWrapWidth() {
+ if (mTable == null || mTable.isDisposed()) {
+ return;
}
- }
-
- private void setScrollToLatestLog(boolean scroll, boolean updateCheckbox) {
- mShouldScrollToLatestLog = scroll;
- if (updateCheckbox) {
- mPauseLogcatCheckBox.setSelection(!scroll);
- }
+ // get width of the last column (log message)
+ TableColumn tc = mTable.getColumn(mTable.getColumnCount() - 1);
+ int colWidth = tc.getWidth();
- if (scroll) {
- mViewer.refresh();
- scrollToLatestLog();
- }
- }
+ // get font width
+ GC gc = new GC(tc.getParent());
+ gc.setFont(mFont);
+ int avgCharWidth = gc.getFontMetrics().getAverageCharWidth();
+ gc.dispose();
- private static class WrappingToolTipSupport extends ColumnViewerToolTipSupport {
- protected WrappingToolTipSupport(ColumnViewer viewer, int style,
- boolean manualActivation) {
- super(viewer, style, manualActivation);
- }
+ int MIN_CHARS_PER_LINE = 50; // show atleast these many chars per line
+ mWrapWidthInChars = Math.max(colWidth/avgCharWidth, MIN_CHARS_PER_LINE);
- @Override
- protected Composite createViewerToolTipContentArea(Event event, ViewerCell cell,
- Composite parent) {
- Composite comp = new Composite(parent, SWT.NONE);
- GridLayout l = new GridLayout(1, false);
- l.horizontalSpacing = 0;
- l.marginWidth = 0;
- l.marginHeight = 0;
- l.verticalSpacing = 0;
- comp.setLayout(l);
-
- Text text = new Text(comp, SWT.BORDER | SWT.V_SCROLL | SWT.WRAP);
- text.setEditable(false);
- text.setText(cell.getElement().toString());
- text.setLayoutData(new GridData(500, 150));
-
- return comp;
- }
+ int OFFSET_AT_END_OF_LINE = 10; // leave some space at the end of the line
+ mWrapWidthInChars -= OFFSET_AT_END_OF_LINE;
+ }
- @Override
- public boolean isHideOnMouseDown() {
- return false;
- }
+ private void setScrollToLatestLog(boolean scroll) {
+ mShouldScrollToLatestLog = scroll;
- public static final void enableFor(ColumnViewer viewer, int style) {
- new WrappingToolTipSupport(viewer, style, false);
+ if (scroll) {
+ scrollToLatestLog();
}
}
@@ -952,7 +849,7 @@ public final class LogCatPanel extends SelectionDependentPanel
* Perform all necessary updates whenever a filter is selected (by user or programmatically).
*/
private void filterSelectionChanged() {
- int idx = getSelectedSavedFilterIndex();
+ int idx = mFiltersTableViewer.getTable().getSelectionIndex();
if (idx == -1) {
/* One of the filters should always be selected.
* On Linux, there is no way to deselect an item.
@@ -971,84 +868,80 @@ public final class LogCatPanel extends SelectionDependentPanel
}
private void resetUnreadCountForSelectedFilter() {
- int index = getSelectedSavedFilterIndex();
- mLogCatFilters.get(index).resetUnreadCount();
-
+ mLogCatFilters.get(mCurrentSelectedFilterIndex).resetUnreadCount();
refreshFiltersTable();
}
- private int getSelectedSavedFilterIndex() {
- return mFiltersTableViewer.getTable().getSelectionIndex();
- }
-
private void updateFiltersToolBar() {
/* The default filter at index 0 can neither be edited, nor removed. */
- boolean en = getSelectedSavedFilterIndex() != 0;
+ boolean en = mCurrentSelectedFilterIndex != DEFAULT_FILTER_INDEX;
mEditFilterToolItem.setEnabled(en);
mDeleteFilterToolItem.setEnabled(en);
}
private void updateAppliedFilters() {
- List<LogCatViewerFilter> filters = getFiltersToApply();
- mViewer.setFilters(filters.toArray(new LogCatViewerFilter[filters.size()]));
-
- /* whenever filters are changed, the number of displayed logs changes
- * drastically. Display the latest log in such a situation. */
- scrollToLatestLog();
+ mCurrentFilters = getFiltersToApply();
+ reloadLogBuffer();
}
- private List<LogCatViewerFilter> getFiltersToApply() {
+ private List<LogCatFilter> getFiltersToApply() {
/* list of filters to apply = saved filter + live filters */
- List<LogCatViewerFilter> filters = new ArrayList<LogCatViewerFilter>();
- filters.add(getSelectedSavedFilter());
+ List<LogCatFilter> filters = new ArrayList<LogCatFilter>();
+
+ if (mCurrentSelectedFilterIndex != DEFAULT_FILTER_INDEX) {
+ filters.add(getSelectedSavedFilter());
+ }
+
filters.addAll(getCurrentLiveFilters());
return filters;
}
- private List<LogCatViewerFilter> getCurrentLiveFilters() {
- List<LogCatViewerFilter> liveFilters = new ArrayList<LogCatViewerFilter>();
-
- List<LogCatFilter> liveFilterSettings = LogCatFilter.fromString(
+ private List<LogCatFilter> getCurrentLiveFilters() {
+ return LogCatFilter.fromString(
mLiveFilterText.getText(), /* current query */
LogLevel.getByString(mLiveFilterLevelCombo.getText())); /* current log level */
- for (LogCatFilter s : liveFilterSettings) {
- liveFilters.add(new LogCatViewerFilter(s));
- }
-
- return liveFilters;
}
- private LogCatViewerFilter getSelectedSavedFilter() {
- int index = getSelectedSavedFilterIndex();
- return new LogCatViewerFilter(mLogCatFilters.get(index));
+ private LogCatFilter getSelectedSavedFilter() {
+ return mLogCatFilters.get(mCurrentSelectedFilterIndex);
}
-
@Override
public void setFocus() {
}
- /**
- * Update view whenever a message is received.
- * @param receivedMessages list of messages from logcat
- * Implements {@link ILogCatMessageEventListener#messageReceived()}.
- */
@Override
- public void messageReceived(List<LogCatMessage> receivedMessages) {
+ public void bufferChanged(List<LogCatMessage> addedMessages,
+ List<LogCatMessage> deletedMessages) {
+
+ synchronized (mLogBuffer) {
+ addedMessages = applyCurrentFilters(addedMessages);
+ deletedMessages = applyCurrentFilters(deletedMessages);
+
+ mLogBuffer.addAll(addedMessages);
+ mDeletedLogCount += deletedMessages.size();
+ }
+
refreshLogCatTable();
+ updateUnreadCount(addedMessages);
+ refreshFiltersTable();
+ }
- if (mShouldScrollToLatestLog) {
- updateUnreadCount(receivedMessages);
- refreshFiltersTable();
- } else {
- LogCatMessageList messageList = mReceiver.getMessages();
- int remainingCapacity = messageList.remainingCapacity();
- if (remainingCapacity == 0) {
- mRemovedEntriesCount +=
- receivedMessages.size() - mPreviousRemainingCapacity;
- }
- mPreviousRemainingCapacity = remainingCapacity;
+ private void reloadLogBuffer() {
+ mTable.removeAll();
+
+ synchronized (mLogBuffer) {
+ mLogBuffer.clear();
+ mDeletedLogCount = 0;
+ }
+
+ if (mReceiver == null) {
+ return;
}
+
+ List<LogCatMessage> addedMessages = mReceiver.getMessages().getAllMessages();
+ List<LogCatMessage> deletedMessages = Collections.emptyList();
+ bufferChanged(addedMessages, deletedMessages);
}
/**
@@ -1089,34 +982,210 @@ public final class LogCatPanel extends SelectionDependentPanel
*/
private void refreshLogCatTable() {
synchronized (this) {
- if (mCurrentRefresher == null && mShouldScrollToLatestLog) {
+ if (mCurrentRefresher == null) {
mCurrentRefresher = new LogCatTableRefresherTask();
Display.getDefault().asyncExec(mCurrentRefresher);
}
}
}
+ /**
+ * The {@link LogCatTableRefresherTask} takes care of refreshing the table with the
+ * new log messages that have been received. Since the log behaves like a circular buffer,
+ * the first step is to remove items from the top of the table (if necessary). This step
+ * is complicated by the fact that a single log message may span multiple rows if the message
+ * was wrapped. Once the deleted items are removed, the new messages are added to the bottom
+ * of the table. If scroll lock is enabled, the item that was original visible is made visible
+ * again, if not, the last item is made visible.
+ */
private class LogCatTableRefresherTask implements Runnable {
@Override
public void run() {
- if (mViewer.getTable().isDisposed()) {
+ if (mTable.isDisposed()) {
return;
}
synchronized (LogCatPanel.this) {
mCurrentRefresher = null;
}
+ // Current topIndex so that it can be restored if scroll locked.
+ int topIndex = mTable.getTopIndex();
+
+ mTable.setRedraw(false);
+
+ // Obtain the list of new messages, and the number of deleted messages.
+ List<LogCatMessage> newMessages;
+ int deletedMessageCount;
+ synchronized (mLogBuffer) {
+ newMessages = new ArrayList<LogCatMessage>(mLogBuffer);
+ mLogBuffer.clear();
+
+ deletedMessageCount = mDeletedLogCount;
+ mDeletedLogCount = 0;
+ }
+
+ int originalItemCount = mTable.getItemCount();
+
+ // Remove entries from the start of the table if they were removed in the log buffer
+ // This is complicated by the fact that a single message may span multiple TableItems
+ // if it was word-wrapped.
+ deletedMessageCount -= removeFromTable(mTable, deletedMessageCount);
+
+ // Compute number of table items that were deleted from the table.
+ int deletedItemCount = originalItemCount - mTable.getItemCount();
+
+ // If there are more messages to delete (after deleting messages from the table),
+ // then delete them from the start of the newly added messages list
+ if (deletedMessageCount > 0) {
+ assert deletedMessageCount < newMessages.size();
+ for (int i = 0; i < deletedMessageCount; i++) {
+ newMessages.remove(0);
+ }
+ }
+
+ // Add the remaining messages to the table.
+ for (LogCatMessage m: newMessages) {
+ List<String> wrappedMessageList = wrapMessage(m.getMessage(), mWrapWidthInChars);
+ Color c = getForegroundColor(m);
+ for (int i = 0; i < wrappedMessageList.size(); i++) {
+ TableItem item = new TableItem(mTable, SWT.NONE);
+
+ if (i == 0) {
+ // Only set the message data in the first item. This allows code that
+ // examines the table item data (such as copy selection) to distinguish
+ // between real messages versus lines that are really just wrapped
+ // content from the previous message.
+ item.setData(m);
+
+ item.setText(new String[] {
+ Character.toString(m.getLogLevel().getPriorityLetter()),
+ m.getTime(),
+ m.getPid(),
+ m.getTid(),
+ m.getAppName(),
+ m.getTag(),
+ wrappedMessageList.get(i)
+ });
+ } else {
+ item.setText(new String[] {
+ "", "", "", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ "", "", "", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ wrappedMessageList.get(i)
+ });
+ }
+ item.setForeground(c);
+ item.setFont(mFont);
+ }
+ }
+
if (mShouldScrollToLatestLog) {
- mViewer.refresh();
scrollToLatestLog();
+ } else {
+ // If scroll locked, show the same item that was original visible in the table.
+ int index = Math.max(topIndex - deletedItemCount, 0);
+ mTable.setTopIndex(index);
+ }
+
+ mTable.setRedraw(true);
+ }
+
+ /**
+ * Removes given number of messages from the table, starting at the top of the table.
+ * Note that the number of messages deleted is not equal to the number of rows
+ * deleted since a single message could span multiple rows. This method first calculates
+ * the number of rows that correspond to the number of messages to delete, and then
+ * removes all those rows.
+ * @param table table from which messages should be removed
+ * @param msgCount number of messages to be removed
+ * @return number of messages that were actually removed
+ */
+ private int removeFromTable(Table table, int msgCount) {
+ int deletedMessageCount = 0; // # of messages that have been deleted
+ int lastItemToDelete = 0; // index of the last item that should be deleted
+
+ while (deletedMessageCount < msgCount && lastItemToDelete < table.getItemCount()) {
+ // only rows that begin a message have their item data set
+ TableItem item = table.getItem(lastItemToDelete);
+ if (item.getData() != null) {
+ deletedMessageCount++;
+ }
+
+ lastItemToDelete++;
+ }
+
+ // If there are any table items left over at the end that are wrapped over from the
+ // previous message, mark them for deletion as well.
+ if (lastItemToDelete < table.getItemCount()
+ && table.getItem(lastItemToDelete).getData() == null) {
+ lastItemToDelete++;
}
+
+ table.remove(0, lastItemToDelete - 1);
+
+ return deletedMessageCount;
}
}
/** Scroll to the last line. */
private void scrollToLatestLog() {
- mRemovedEntriesCount = 0;
- mViewer.getTable().setTopIndex(mViewer.getTable().getItemCount() - 1);
+ mTable.setTopIndex(mTable.getItemCount() - 1);
+ }
+
+ /**
+ * Splits the message into multiple lines if the message length exceeds given width.
+ * If the message was split, then a wrap character \u23ce is appended to the end of all
+ * lines but the last one.
+ */
+ private List<String> wrapMessage(String msg, int wrapWidth) {
+ if (msg.length() < wrapWidth) {
+ return Collections.singletonList(msg);
+ }
+
+ List<String> wrappedMessages = new ArrayList<String>();
+
+ int offset = 0;
+ int len = msg.length();
+
+ while (len > 0) {
+ int copylen = Math.min(wrapWidth, len);
+ String s = msg.substring(offset, offset + copylen);
+
+ offset += copylen;
+ len -= copylen;
+
+ if (len > 0) { // if there are more lines following, then append a wrap marker
+ s += " \u23ce"; //$NON-NLS-1$
+ }
+
+ wrappedMessages.add(s);
+ }
+
+ return wrappedMessages;
+ }
+
+ /* Default Colors for different log levels. */
+ private static final Color INFO_MSG_COLOR = new Color(null, 0, 127, 0);
+ private static final Color DEBUG_MSG_COLOR = new Color(null, 0, 0, 127);
+ private static final Color ERROR_MSG_COLOR = new Color(null, 255, 0, 0);
+ private static final Color WARN_MSG_COLOR = new Color(null, 255, 127, 0);
+ private static final Color VERBOSE_MSG_COLOR = new Color(null, 0, 0, 0);
+
+ private static Color getForegroundColor(LogCatMessage m) {
+ LogLevel l = m.getLogLevel();
+
+ if (l.equals(LogLevel.VERBOSE)) {
+ return VERBOSE_MSG_COLOR;
+ } else if (l.equals(LogLevel.INFO)) {
+ return INFO_MSG_COLOR;
+ } else if (l.equals(LogLevel.DEBUG)) {
+ return DEBUG_MSG_COLOR;
+ } else if (l.equals(LogLevel.ERROR)) {
+ return ERROR_MSG_COLOR;
+ } else if (l.equals(LogLevel.WARN)) {
+ return WARN_MSG_COLOR;
+ }
+
+ return null;
}
private List<ILogCatMessageSelectionListener> mMessageSelectionListeners;
@@ -1124,7 +1193,7 @@ public final class LogCatPanel extends SelectionDependentPanel
private void initDoubleClickListener() {
mMessageSelectionListeners = new ArrayList<ILogCatMessageSelectionListener>(1);
- mViewer.getTable().addSelectionListener(new SelectionAdapter() {
+ mTable.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetDefaultSelected(SelectionEvent arg0) {
List<LogCatMessage> selectedMessages = getSelectedLogCatMessages();
@@ -1153,7 +1222,6 @@ public final class LogCatPanel extends SelectionDependentPanel
public void setTableFocusListener(ITableFocusListener listener) {
mTableFocusListener = listener;
- final Table table = mViewer.getTable();
final IFocusedTableActivator activator = new IFocusedTableActivator() {
@Override
public void copy(Clipboard clipboard) {
@@ -1162,11 +1230,11 @@ public final class LogCatPanel extends SelectionDependentPanel
@Override
public void selectAll() {
- table.selectAll();
+ mTable.selectAll();
}
};
- table.addFocusListener(new FocusListener() {
+ mTable.addFocusListener(new FocusListener() {
@Override
public void focusGained(FocusEvent e) {
mTableFocusListener.focusGained(activator);
@@ -1198,6 +1266,6 @@ public final class LogCatPanel extends SelectionDependentPanel
/** Select all items in the logcat table. */
public void selectAll() {
- mViewer.getTable().selectAll();
+ mTable.selectAll();
}
}
diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatReceiver.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatReceiver.java
index c9606f6..8f2d52c 100644
--- a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatReceiver.java
+++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatReceiver.java
@@ -38,7 +38,7 @@ public final class LogCatReceiver {
private LogCatMessageList mLogMessages;
private IDevice mCurrentDevice;
private LogCatOutputReceiver mCurrentLogCatOutputReceiver;
- private Set<ILogCatMessageEventListener> mLogCatMessageListeners;
+ private Set<ILogCatBufferChangeListener> mLogCatMessageListeners;
private LogCatMessageParser mLogCatMessageParser;
private LogCatPidToNameMapper mPidToNameMapper;
private IPreferenceStore mPrefStore;
@@ -55,7 +55,7 @@ public final class LogCatReceiver {
mCurrentDevice = device;
mPrefStore = prefStore;
- mLogCatMessageListeners = new HashSet<ILogCatMessageEventListener>();
+ mLogCatMessageListeners = new HashSet<ILogCatBufferChangeListener>();
mLogCatMessageParser = new LogCatMessageParser();
mPidToNameMapper = new LogCatPidToNameMapper(mCurrentDevice);
@@ -147,14 +147,16 @@ public final class LogCatReceiver {
}
private void processLogLines(String[] lines) {
- List<LogCatMessage> messages = mLogCatMessageParser.processLogLines(lines,
+ List<LogCatMessage> newMessages = mLogCatMessageParser.processLogLines(lines,
mPidToNameMapper);
- if (messages.size() > 0) {
- for (LogCatMessage m : messages) {
- mLogMessages.appendMessage(m);
+ if (newMessages.size() > 0) {
+ List<LogCatMessage> deletedMessages;
+ synchronized (mLogMessages) {
+ deletedMessages = mLogMessages.ensureSpace(newMessages.size());
+ mLogMessages.appendMessages(newMessages);
}
- sendMessageReceivedEvent(messages);
+ sendLogChangedEvent(newMessages, deletedMessages);
}
}
@@ -177,17 +179,18 @@ public final class LogCatReceiver {
* Add to list of message event listeners.
* @param l listener to notified when messages are received from the device
*/
- public void addMessageReceivedEventListener(ILogCatMessageEventListener l) {
+ public void addMessageReceivedEventListener(ILogCatBufferChangeListener l) {
mLogCatMessageListeners.add(l);
}
- public void removeMessageReceivedEventListener(ILogCatMessageEventListener l) {
+ public void removeMessageReceivedEventListener(ILogCatBufferChangeListener l) {
mLogCatMessageListeners.remove(l);
}
- private void sendMessageReceivedEvent(List<LogCatMessage> messages) {
- for (ILogCatMessageEventListener l : mLogCatMessageListeners) {
- l.messageReceived(messages);
+ private void sendLogChangedEvent(List<LogCatMessage> addedMessages,
+ List<LogCatMessage> deletedMessages) {
+ for (ILogCatBufferChangeListener l : mLogCatMessageListeners) {
+ l.bufferChanged(addedMessages, deletedMessages);
}
}
diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatViewerFilter.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatViewerFilter.java
deleted file mode 100644
index f7b8dce..0000000
--- a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatViewerFilter.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.ddmuilib.logcat;
-
-import org.eclipse.jface.viewers.Viewer;
-import org.eclipse.jface.viewers.ViewerFilter;
-
-/**
- * A JFace {@link ViewerFilter} for the {@link LogCatPanel} displaying logcat messages.
- * This is a simple wrapper around {@link LogCatFilter}.
- */
-public final class LogCatViewerFilter extends ViewerFilter {
- private LogCatFilter mFilter;
-
- /**
- * Construct a {@link ViewerFilter} filtering logcat messages based on
- * user provided filter settings for PID, Tag and log level.
- * @param filter filter to use
- */
- public LogCatViewerFilter(LogCatFilter filter) {
- mFilter = filter;
- }
-
- @Override
- public boolean select(Viewer viewer, Object parent, Object element) {
- if (!(element instanceof LogCatMessage)) {
- return false;
- }
-
- LogCatMessage m = (LogCatMessage) element;
- return mFilter.matches(m);
- }
-}