1 /* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.ddmuilib.logcat; 18 19 import com.android.ddmlib.AdbCommandRejectedException; 20 import com.android.ddmlib.IDevice; 21 import com.android.ddmlib.Log; 22 import com.android.ddmlib.Log.LogLevel; 23 import com.android.ddmlib.MultiLineReceiver; 24 import com.android.ddmlib.ShellCommandUnresponsiveException; 25 import com.android.ddmlib.TimeoutException; 26 import com.android.ddmuilib.DdmUiPreferences; 27 import com.android.ddmuilib.ITableFocusListener; 28 import com.android.ddmuilib.ITableFocusListener.IFocusedTableActivator; 29 import com.android.ddmuilib.SelectionDependentPanel; 30 import com.android.ddmuilib.TableHelper; 31 import com.android.ddmuilib.actions.ICommonAction; 32 33 import org.eclipse.jface.preference.IPreferenceStore; 34 import org.eclipse.swt.SWT; 35 import org.eclipse.swt.SWTException; 36 import org.eclipse.swt.dnd.Clipboard; 37 import org.eclipse.swt.dnd.TextTransfer; 38 import org.eclipse.swt.dnd.Transfer; 39 import org.eclipse.swt.events.ControlEvent; 40 import org.eclipse.swt.events.ControlListener; 41 import org.eclipse.swt.events.FocusEvent; 42 import org.eclipse.swt.events.FocusListener; 43 import org.eclipse.swt.events.ModifyEvent; 44 import org.eclipse.swt.events.ModifyListener; 45 import org.eclipse.swt.events.SelectionAdapter; 46 import org.eclipse.swt.events.SelectionEvent; 47 import org.eclipse.swt.graphics.Font; 48 import org.eclipse.swt.graphics.Rectangle; 49 import org.eclipse.swt.layout.FillLayout; 50 import org.eclipse.swt.layout.GridData; 51 import org.eclipse.swt.layout.GridLayout; 52 import org.eclipse.swt.widgets.Composite; 53 import org.eclipse.swt.widgets.Control; 54 import org.eclipse.swt.widgets.Display; 55 import org.eclipse.swt.widgets.FileDialog; 56 import org.eclipse.swt.widgets.Label; 57 import org.eclipse.swt.widgets.TabFolder; 58 import org.eclipse.swt.widgets.TabItem; 59 import org.eclipse.swt.widgets.Table; 60 import org.eclipse.swt.widgets.TableColumn; 61 import org.eclipse.swt.widgets.TableItem; 62 import org.eclipse.swt.widgets.Text; 63 64 import java.io.FileWriter; 65 import java.io.IOException; 66 import java.util.ArrayList; 67 import java.util.Arrays; 68 import java.util.regex.Matcher; 69 import java.util.regex.Pattern; 70 71 public class LogPanel extends SelectionDependentPanel { 72 73 private static final int STRING_BUFFER_LENGTH = 10000; 74 75 /** no filtering. Only one tab with everything. */ 76 public static final int FILTER_NONE = 0; 77 /** manual mode for filter. all filters are manually created. */ 78 public static final int FILTER_MANUAL = 1; 79 /** automatic mode for filter (pid mode). 80 * All filters are automatically created. */ 81 public static final int FILTER_AUTO_PID = 2; 82 /** automatic mode for filter (tag mode). 83 * All filters are automatically created. */ 84 public static final int FILTER_AUTO_TAG = 3; 85 /** Manual filtering mode + new filter for debug app, if needed */ 86 public static final int FILTER_DEBUG = 4; 87 88 public static final int COLUMN_MODE_MANUAL = 0; 89 public static final int COLUMN_MODE_AUTO = 1; 90 91 public static String PREFS_TIME; 92 public static String PREFS_LEVEL; 93 public static String PREFS_PID; 94 public static String PREFS_TAG; 95 public static String PREFS_MESSAGE; 96 97 /** 98 * This pattern is meant to parse the first line of a log message with the option 99 * 'logcat -v long'. The first line represents the date, tag, severity, etc.. while the 100 * following lines are the message (can be several line).<br> 101 * This first line looks something like<br> 102 * <code>"[ 00-00 00:00:00.000 <pid>:0x<???> <severity>/<tag>]"</code> 103 * <br> 104 * Note: severity is one of V, D, I, W, or EM<br> 105 * Note: the fraction of second value can have any number of digit. 106 * Note the tag should be trim as it may have spaces at the end. 107 */ 108 private static Pattern sLogPattern = Pattern.compile( 109 "^\\[\\s(\\d\\d-\\d\\d\\s\\d\\d:\\d\\d:\\d\\d\\.\\d+)" + //$NON-NLS-1$ 110 "\\s+(\\d*):(0x[0-9a-fA-F]+)\\s([VDIWE])/(.*)\\]$"); //$NON-NLS-1$ 111 112 /** 113 * Interface for Storage Filter manager. Implementation of this interface 114 * provide a custom way to archive an reload filters. 115 */ 116 public interface ILogFilterStorageManager { 117 getFilterFromStore()118 public LogFilter[] getFilterFromStore(); 119 saveFilters(LogFilter[] filters)120 public void saveFilters(LogFilter[] filters); 121 requiresDefaultFilter()122 public boolean requiresDefaultFilter(); 123 } 124 125 private Composite mParent; 126 private IPreferenceStore mStore; 127 128 /** top object in the view */ 129 private TabFolder mFolders; 130 131 private LogColors mColors; 132 133 private ILogFilterStorageManager mFilterStorage; 134 135 private LogCatOuputReceiver mCurrentLogCat; 136 137 /** 138 * Circular buffer containing the logcat output. This is unfiltered. 139 * The valid content goes from <code>mBufferStart</code> to 140 * <code>mBufferEnd - 1</code>. Therefore its number of item is 141 * <code>mBufferEnd - mBufferStart</code>. 142 */ 143 private LogMessage[] mBuffer = new LogMessage[STRING_BUFFER_LENGTH]; 144 145 /** Represents the oldest message in the buffer */ 146 private int mBufferStart = -1; 147 148 /** 149 * Represents the next usable item in the buffer to receive new message. 150 * This can be equal to mBufferStart, but when used mBufferStart will be 151 * incremented as well. 152 */ 153 private int mBufferEnd = -1; 154 155 /** Filter list */ 156 private LogFilter[] mFilters; 157 158 /** Default filter */ 159 private LogFilter mDefaultFilter; 160 161 /** Current filter being displayed */ 162 private LogFilter mCurrentFilter; 163 164 /** Filtering mode */ 165 private int mFilterMode = FILTER_NONE; 166 167 /** Device currently running logcat */ 168 private IDevice mCurrentLoggedDevice = null; 169 170 private ICommonAction mDeleteFilterAction; 171 private ICommonAction mEditFilterAction; 172 173 private ICommonAction[] mLogLevelActions; 174 175 /** message data, separated from content for multi line messages */ 176 protected static class LogMessageInfo { 177 public LogLevel logLevel; 178 public int pid; 179 public String pidString; 180 public String tag; 181 public String time; 182 } 183 184 /** pointer to the latest LogMessageInfo. this is used for multi line 185 * log message, to reuse the info regarding level, pid, etc... 186 */ 187 private LogMessageInfo mLastMessageInfo = null; 188 189 private boolean mPendingAsyncRefresh = false; 190 191 private String mDefaultLogSave; 192 193 private int mColumnMode = COLUMN_MODE_MANUAL; 194 private Font mDisplayFont; 195 196 private ITableFocusListener mGlobalListener; 197 198 private LogCatViewInterface mLogCatViewInterface = null; 199 200 /** message data, separated from content for multi line messages */ 201 protected static class LogMessage { 202 public LogMessageInfo data; 203 public String msg; 204 205 @Override toString()206 public String toString() { 207 return data.time + ": " //$NON-NLS-1$ 208 + data.logLevel + "/" //$NON-NLS-1$ 209 + data.tag + "(" //$NON-NLS-1$ 210 + data.pidString + "): " //$NON-NLS-1$ 211 + msg; 212 } 213 } 214 215 /** 216 * objects able to receive the output of a remote shell command, 217 * specifically a logcat command in this case 218 */ 219 private final class LogCatOuputReceiver extends MultiLineReceiver { 220 221 public boolean isCancelled = false; 222 LogCatOuputReceiver()223 public LogCatOuputReceiver() { 224 super(); 225 226 setTrimLine(false); 227 } 228 229 @Override processNewLines(String[] lines)230 public void processNewLines(String[] lines) { 231 if (isCancelled == false) { 232 processLogLines(lines); 233 } 234 } 235 236 @Override isCancelled()237 public boolean isCancelled() { 238 return isCancelled; 239 } 240 } 241 242 /** 243 * Parser class for the output of a "ps" shell command executed on a device. 244 * This class looks for a specific pid to find the process name from it. 245 * Once found, the name is used to update a filter and a tab object 246 * 247 */ 248 private class PsOutputReceiver extends MultiLineReceiver { 249 250 private LogFilter mFilter; 251 252 private TabItem mTabItem; 253 254 private int mPid; 255 256 /** set to true when we've found the pid we're looking for */ 257 private boolean mDone = false; 258 PsOutputReceiver(int pid, LogFilter filter, TabItem tabItem)259 PsOutputReceiver(int pid, LogFilter filter, TabItem tabItem) { 260 mPid = pid; 261 mFilter = filter; 262 mTabItem = tabItem; 263 } 264 265 @Override isCancelled()266 public boolean isCancelled() { 267 return mDone; 268 } 269 270 @Override processNewLines(String[] lines)271 public void processNewLines(String[] lines) { 272 for (String line : lines) { 273 if (line.startsWith("USER")) { //$NON-NLS-1$ 274 continue; 275 } 276 // get the pid. 277 int index = line.indexOf(' '); 278 if (index == -1) { 279 continue; 280 } 281 // look for the next non blank char 282 index++; 283 while (line.charAt(index) == ' ') { 284 index++; 285 } 286 287 // this is the start of the pid. 288 // look for the end. 289 int index2 = line.indexOf(' ', index); 290 291 // get the line 292 String pidStr = line.substring(index, index2); 293 int pid = Integer.parseInt(pidStr); 294 if (pid != mPid) { 295 continue; 296 } else { 297 // get the process name 298 index = line.lastIndexOf(' '); 299 final String name = line.substring(index + 1); 300 301 mFilter.setName(name); 302 303 // update the tab 304 Display d = mFolders.getDisplay(); 305 d.asyncExec(new Runnable() { 306 @Override 307 public void run() { 308 mTabItem.setText(name); 309 } 310 }); 311 312 // we're done with this ps. 313 mDone = true; 314 return; 315 } 316 } 317 } 318 319 } 320 321 /** 322 * Interface implemented by the LogCatView in Eclipse for particular action on double-click. 323 */ 324 public interface LogCatViewInterface { onDoubleClick()325 public void onDoubleClick(); 326 } 327 328 /** 329 * Create the log view with some default parameters 330 * @param colors The display color object 331 * @param filterStorage the storage for user defined filters. 332 * @param mode The filtering mode 333 */ LogPanel(LogColors colors, ILogFilterStorageManager filterStorage, int mode)334 public LogPanel(LogColors colors, 335 ILogFilterStorageManager filterStorage, int mode) { 336 mColors = colors; 337 mFilterMode = mode; 338 mFilterStorage = filterStorage; 339 mStore = DdmUiPreferences.getStore(); 340 } 341 setActions(ICommonAction deleteAction, ICommonAction editAction, ICommonAction[] logLevelActions)342 public void setActions(ICommonAction deleteAction, ICommonAction editAction, 343 ICommonAction[] logLevelActions) { 344 mDeleteFilterAction = deleteAction; 345 mEditFilterAction = editAction; 346 mLogLevelActions = logLevelActions; 347 } 348 349 /** 350 * Sets the column mode. Must be called before creatUI 351 * @param mode the column mode. Valid values are COLUMN_MOD_MANUAL and 352 * COLUMN_MODE_AUTO 353 */ setColumnMode(int mode)354 public void setColumnMode(int mode) { 355 mColumnMode = mode; 356 } 357 358 /** 359 * Sets the display font. 360 * @param font The display font. 361 */ setFont(Font font)362 public void setFont(Font font) { 363 mDisplayFont = font; 364 365 if (mFilters != null) { 366 for (LogFilter f : mFilters) { 367 Table table = f.getTable(); 368 if (table != null) { 369 table.setFont(font); 370 } 371 } 372 } 373 374 if (mDefaultFilter != null) { 375 Table table = mDefaultFilter.getTable(); 376 if (table != null) { 377 table.setFont(font); 378 } 379 } 380 } 381 382 /** 383 * Sent when a new device is selected. The new device can be accessed 384 * with {@link #getCurrentDevice()}. 385 */ 386 @Override deviceSelected()387 public void deviceSelected() { 388 startLogCat(getCurrentDevice()); 389 } 390 391 /** 392 * Sent when a new client is selected. The new client can be accessed 393 * with {@link #getCurrentClient()}. 394 */ 395 @Override clientSelected()396 public void clientSelected() { 397 // pass 398 } 399 400 401 /** 402 * Creates a control capable of displaying some information. This is 403 * called once, when the application is initializing, from the UI thread. 404 */ 405 @Override createControl(Composite parent)406 protected Control createControl(Composite parent) { 407 mParent = parent; 408 409 Composite top = new Composite(parent, SWT.NONE); 410 top.setLayoutData(new GridData(GridData.FILL_BOTH)); 411 top.setLayout(new GridLayout(1, false)); 412 413 // create the tab folder 414 mFolders = new TabFolder(top, SWT.NONE); 415 mFolders.setLayoutData(new GridData(GridData.FILL_BOTH)); 416 mFolders.addSelectionListener(new SelectionAdapter() { 417 @Override 418 public void widgetSelected(SelectionEvent e) { 419 if (mCurrentFilter != null) { 420 mCurrentFilter.setSelectedState(false); 421 } 422 mCurrentFilter = getCurrentFilter(); 423 mCurrentFilter.setSelectedState(true); 424 updateColumns(mCurrentFilter.getTable()); 425 if (mCurrentFilter.getTempFilterStatus()) { 426 initFilter(mCurrentFilter); 427 } 428 selectionChanged(mCurrentFilter); 429 } 430 }); 431 432 433 Composite bottom = new Composite(top, SWT.NONE); 434 bottom.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); 435 bottom.setLayout(new GridLayout(3, false)); 436 437 Label label = new Label(bottom, SWT.NONE); 438 label.setText("Filter:"); 439 440 final Text filterText = new Text(bottom, SWT.SINGLE | SWT.BORDER); 441 filterText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); 442 filterText.addModifyListener(new ModifyListener() { 443 @Override 444 public void modifyText(ModifyEvent e) { 445 updateFilteringWith(filterText.getText()); 446 } 447 }); 448 449 /* 450 Button addFilterBtn = new Button(bottom, SWT.NONE); 451 addFilterBtn.setImage(mImageLoader.loadImage("add.png", //$NON-NLS-1$ 452 addFilterBtn.getDisplay())); 453 */ 454 455 // get the filters 456 createFilters(); 457 458 // for each filter, create a tab. 459 int index = 0; 460 461 if (mDefaultFilter != null) { 462 createTab(mDefaultFilter, index++, false); 463 } 464 465 if (mFilters != null) { 466 for (LogFilter f : mFilters) { 467 createTab(f, index++, false); 468 } 469 } 470 471 return top; 472 } 473 474 @Override postCreation()475 protected void postCreation() { 476 // pass 477 } 478 479 /** 480 * Sets the focus to the proper object. 481 */ 482 @Override setFocus()483 public void setFocus() { 484 mFolders.setFocus(); 485 } 486 487 488 /** 489 * Starts a new logcat and set mCurrentLogCat as the current receiver. 490 * @param device the device to connect logcat to. 491 */ startLogCat(final IDevice device)492 public void startLogCat(final IDevice device) { 493 if (device == mCurrentLoggedDevice) { 494 return; 495 } 496 497 // if we have a logcat already running 498 if (mCurrentLoggedDevice != null) { 499 stopLogCat(false); 500 mCurrentLoggedDevice = null; 501 } 502 503 resetUI(false); 504 505 if (device != null) { 506 // create a new output receiver 507 mCurrentLogCat = new LogCatOuputReceiver(); 508 509 // start the logcat in a different thread 510 new Thread("Logcat") { //$NON-NLS-1$ 511 @Override 512 public void run() { 513 514 while (device.isOnline() == false && 515 mCurrentLogCat != null && 516 mCurrentLogCat.isCancelled == false) { 517 try { 518 sleep(2000); 519 } catch (InterruptedException e) { 520 return; 521 } 522 } 523 524 if (mCurrentLogCat == null || mCurrentLogCat.isCancelled) { 525 // logcat was stopped/cancelled before the device became ready. 526 return; 527 } 528 529 try { 530 mCurrentLoggedDevice = device; 531 device.executeShellCommand("logcat -v long", mCurrentLogCat, 0 /*timeout*/); //$NON-NLS-1$ 532 } catch (Exception e) { 533 Log.e("Logcat", e); 534 } finally { 535 // at this point the command is terminated. 536 mCurrentLogCat = null; 537 mCurrentLoggedDevice = null; 538 } 539 } 540 }.start(); 541 } 542 } 543 544 /** Stop the current logcat */ stopLogCat(boolean inUiThread)545 public void stopLogCat(boolean inUiThread) { 546 if (mCurrentLogCat != null) { 547 mCurrentLogCat.isCancelled = true; 548 549 // when the thread finishes, no one will reference that object 550 // and it'll be destroyed 551 mCurrentLogCat = null; 552 553 // reset the content buffer 554 for (int i = 0 ; i < STRING_BUFFER_LENGTH; i++) { 555 mBuffer[i] = null; 556 } 557 558 // because it's a circular buffer, it's hard to know if 559 // the array is empty with both start/end at 0 or if it's full 560 // with both start/end at 0 as well. So to mean empty, we use -1 561 mBufferStart = -1; 562 mBufferEnd = -1; 563 564 resetFilters(); 565 resetUI(inUiThread); 566 } 567 } 568 569 /** 570 * Adds a new Filter. This methods displays the UI to create the filter 571 * and set up its parameters.<br> 572 * <b>MUST</b> be called from the ui thread. 573 * 574 */ addFilter()575 public void addFilter() { 576 EditFilterDialog dlg = new EditFilterDialog(mFolders.getShell()); 577 if (dlg.open()) { 578 synchronized (mBuffer) { 579 // get the new filter in the array 580 LogFilter filter = dlg.getFilter(); 581 addFilterToArray(filter); 582 583 int index = mFilters.length - 1; 584 if (mDefaultFilter != null) { 585 index++; 586 } 587 588 if (false) { 589 590 for (LogFilter f : mFilters) { 591 if (f.uiReady()) { 592 f.dispose(); 593 } 594 } 595 if (mDefaultFilter != null && mDefaultFilter.uiReady()) { 596 mDefaultFilter.dispose(); 597 } 598 599 // for each filter, create a tab. 600 int i = 0; 601 if (mFilters != null) { 602 for (LogFilter f : mFilters) { 603 createTab(f, i++, true); 604 } 605 } 606 if (mDefaultFilter != null) { 607 createTab(mDefaultFilter, i++, true); 608 } 609 } else { 610 611 // create ui for the filter. 612 createTab(filter, index, true); 613 614 // reset the default as it shouldn't contain the content of 615 // this new filter. 616 if (mDefaultFilter != null) { 617 initDefaultFilter(); 618 } 619 } 620 621 // select the new filter 622 if (mCurrentFilter != null) { 623 mCurrentFilter.setSelectedState(false); 624 } 625 mFolders.setSelection(index); 626 filter.setSelectedState(true); 627 mCurrentFilter = filter; 628 629 selectionChanged(filter); 630 631 // finally we update the filtering mode if needed 632 if (mFilterMode == FILTER_NONE) { 633 mFilterMode = FILTER_MANUAL; 634 } 635 636 mFilterStorage.saveFilters(mFilters); 637 638 } 639 } 640 } 641 642 /** 643 * Edits the current filter. The method displays the UI to edit the filter. 644 */ editFilter()645 public void editFilter() { 646 if (mCurrentFilter != null && mCurrentFilter != mDefaultFilter) { 647 EditFilterDialog dlg = new EditFilterDialog( 648 mFolders.getShell(), mCurrentFilter); 649 if (dlg.open()) { 650 synchronized (mBuffer) { 651 // at this point the filter has been updated. 652 // so we update its content 653 initFilter(mCurrentFilter); 654 655 // and the content of the "other" filter as well. 656 if (mDefaultFilter != null) { 657 initDefaultFilter(); 658 } 659 660 mFilterStorage.saveFilters(mFilters); 661 } 662 } 663 } 664 } 665 666 /** 667 * Deletes the current filter. 668 */ deleteFilter()669 public void deleteFilter() { 670 synchronized (mBuffer) { 671 if (mCurrentFilter != null && mCurrentFilter != mDefaultFilter) { 672 // remove the filter from the list 673 removeFilterFromArray(mCurrentFilter); 674 mCurrentFilter.dispose(); 675 676 // select the new filter 677 mFolders.setSelection(0); 678 if (mFilters.length > 0) { 679 mCurrentFilter = mFilters[0]; 680 } else { 681 mCurrentFilter = mDefaultFilter; 682 } 683 684 selectionChanged(mCurrentFilter); 685 686 // update the content of the "other" filter to include what was filtered out 687 // by the deleted filter. 688 if (mDefaultFilter != null) { 689 initDefaultFilter(); 690 } 691 692 mFilterStorage.saveFilters(mFilters); 693 } 694 } 695 } 696 697 /** 698 * saves the current selection in a text file. 699 * @return false if the saving failed. 700 */ save()701 public boolean save() { 702 synchronized (mBuffer) { 703 FileDialog dlg = new FileDialog(mParent.getShell(), SWT.SAVE); 704 String fileName; 705 706 dlg.setText("Save log..."); 707 dlg.setFileName("log.txt"); 708 String defaultPath = mDefaultLogSave; 709 if (defaultPath == null) { 710 defaultPath = System.getProperty("user.home"); //$NON-NLS-1$ 711 } 712 dlg.setFilterPath(defaultPath); 713 dlg.setFilterNames(new String[] { 714 "Text Files (*.txt)" 715 }); 716 dlg.setFilterExtensions(new String[] { 717 "*.txt" 718 }); 719 720 fileName = dlg.open(); 721 if (fileName != null) { 722 mDefaultLogSave = dlg.getFilterPath(); 723 724 // get the current table and its selection 725 Table currentTable = mCurrentFilter.getTable(); 726 727 int[] selection = currentTable.getSelectionIndices(); 728 729 // we need to sort the items to be sure. 730 Arrays.sort(selection); 731 732 // loop on the selection and output the file. 733 try { 734 FileWriter writer = new FileWriter(fileName); 735 736 for (int i : selection) { 737 TableItem item = currentTable.getItem(i); 738 LogMessage msg = (LogMessage)item.getData(); 739 String line = msg.toString(); 740 writer.write(line); 741 writer.write('\n'); 742 } 743 writer.flush(); 744 745 } catch (IOException e) { 746 return false; 747 } 748 } 749 } 750 751 return true; 752 } 753 754 /** 755 * Empty the current circular buffer. 756 */ clear()757 public void clear() { 758 synchronized (mBuffer) { 759 for (int i = 0 ; i < STRING_BUFFER_LENGTH; i++) { 760 mBuffer[i] = null; 761 } 762 763 mBufferStart = -1; 764 mBufferEnd = -1; 765 766 // now we clear the existing filters 767 for (LogFilter filter : mFilters) { 768 filter.clear(); 769 } 770 771 // and the default one 772 if (mDefaultFilter != null) { 773 mDefaultFilter.clear(); 774 } 775 } 776 } 777 778 /** 779 * Copies the current selection of the current filter as multiline text. 780 * 781 * @param clipboard The clipboard to place the copied content. 782 */ copy(Clipboard clipboard)783 public void copy(Clipboard clipboard) { 784 // get the current table and its selection 785 Table currentTable = mCurrentFilter.getTable(); 786 787 copyTable(clipboard, currentTable); 788 } 789 790 /** 791 * Selects all lines. 792 */ selectAll()793 public void selectAll() { 794 Table currentTable = mCurrentFilter.getTable(); 795 currentTable.selectAll(); 796 } 797 798 /** 799 * Sets a TableFocusListener which will be notified when one of the tables 800 * gets or loses focus. 801 * 802 * @param listener 803 */ setTableFocusListener(ITableFocusListener listener)804 public void setTableFocusListener(ITableFocusListener listener) { 805 // record the global listener, to make sure table created after 806 // this call will still be setup. 807 mGlobalListener = listener; 808 809 // now we setup the existing filters 810 for (LogFilter filter : mFilters) { 811 Table table = filter.getTable(); 812 813 addTableToFocusListener(table); 814 } 815 816 // and the default one 817 if (mDefaultFilter != null) { 818 addTableToFocusListener(mDefaultFilter.getTable()); 819 } 820 } 821 822 /** 823 * Sets up a Table object to notify the global Table Focus listener when it 824 * gets or loses the focus. 825 * 826 * @param table the Table object. 827 */ addTableToFocusListener(final Table table)828 private void addTableToFocusListener(final Table table) { 829 // create the activator for this table 830 final IFocusedTableActivator activator = new IFocusedTableActivator() { 831 @Override 832 public void copy(Clipboard clipboard) { 833 copyTable(clipboard, table); 834 } 835 836 @Override 837 public void selectAll() { 838 table.selectAll(); 839 } 840 }; 841 842 // add the focus listener on the table to notify the global listener 843 table.addFocusListener(new FocusListener() { 844 @Override 845 public void focusGained(FocusEvent e) { 846 mGlobalListener.focusGained(activator); 847 } 848 849 @Override 850 public void focusLost(FocusEvent e) { 851 mGlobalListener.focusLost(activator); 852 } 853 }); 854 } 855 856 /** 857 * Copies the current selection of a Table into the provided Clipboard, as 858 * multi-line text. 859 * 860 * @param clipboard The clipboard to place the copied content. 861 * @param table The table to copy from. 862 */ copyTable(Clipboard clipboard, Table table)863 private static void copyTable(Clipboard clipboard, Table table) { 864 int[] selection = table.getSelectionIndices(); 865 866 // we need to sort the items to be sure. 867 Arrays.sort(selection); 868 869 // all lines must be concatenated. 870 StringBuilder sb = new StringBuilder(); 871 872 // loop on the selection and output the file. 873 for (int i : selection) { 874 TableItem item = table.getItem(i); 875 LogMessage msg = (LogMessage)item.getData(); 876 String line = msg.toString(); 877 sb.append(line); 878 sb.append('\n'); 879 } 880 881 // now add that to the clipboard 882 clipboard.setContents(new Object[] { 883 sb.toString() 884 }, new Transfer[] { 885 TextTransfer.getInstance() 886 }); 887 } 888 889 /** 890 * Sets the log level for the current filter, but does not save it. 891 * @param i 892 */ setCurrentFilterLogLevel(int i)893 public void setCurrentFilterLogLevel(int i) { 894 LogFilter filter = getCurrentFilter(); 895 896 filter.setLogLevel(i); 897 898 initFilter(filter); 899 } 900 901 /** 902 * Creates a new tab in the folderTab item. Must be called from the ui 903 * thread. 904 * @param filter The filter associated with the tab. 905 * @param index the index of the tab. if -1, the tab will be added at the 906 * end. 907 * @param fillTable If true the table is filled with the current content of 908 * the buffer. 909 * @return The TabItem object that was created. 910 */ createTab(LogFilter filter, int index, boolean fillTable)911 private TabItem createTab(LogFilter filter, int index, boolean fillTable) { 912 synchronized (mBuffer) { 913 TabItem item = null; 914 if (index != -1) { 915 item = new TabItem(mFolders, SWT.NONE, index); 916 } else { 917 item = new TabItem(mFolders, SWT.NONE); 918 } 919 item.setText(filter.getName()); 920 921 // set the control (the parent is the TabFolder item, always) 922 Composite top = new Composite(mFolders, SWT.NONE); 923 item.setControl(top); 924 925 top.setLayout(new FillLayout()); 926 927 // create the ui, first the table 928 final Table t = new Table(top, SWT.MULTI | SWT.FULL_SELECTION); 929 t.addSelectionListener(new SelectionAdapter() { 930 @Override 931 public void widgetDefaultSelected(SelectionEvent e) { 932 if (mLogCatViewInterface != null) { 933 mLogCatViewInterface.onDoubleClick(); 934 } 935 } 936 }); 937 938 if (mDisplayFont != null) { 939 t.setFont(mDisplayFont); 940 } 941 942 // give the ui objects to the filters. 943 filter.setWidgets(item, t); 944 945 t.setHeaderVisible(true); 946 t.setLinesVisible(false); 947 948 if (mGlobalListener != null) { 949 addTableToFocusListener(t); 950 } 951 952 // create a controllistener that will handle the resizing of all the 953 // columns (except the last) and of the table itself. 954 ControlListener listener = null; 955 if (mColumnMode == COLUMN_MODE_AUTO) { 956 listener = new ControlListener() { 957 @Override 958 public void controlMoved(ControlEvent e) { 959 } 960 961 @Override 962 public void controlResized(ControlEvent e) { 963 Rectangle r = t.getClientArea(); 964 965 // get the size of all but the last column 966 int total = t.getColumn(0).getWidth(); 967 total += t.getColumn(1).getWidth(); 968 total += t.getColumn(2).getWidth(); 969 total += t.getColumn(3).getWidth(); 970 971 if (r.width > total) { 972 t.getColumn(4).setWidth(r.width-total); 973 } 974 } 975 }; 976 977 t.addControlListener(listener); 978 } 979 980 // then its column 981 TableColumn col = TableHelper.createTableColumn(t, "Time", SWT.LEFT, 982 "00-00 00:00:00", //$NON-NLS-1$ 983 PREFS_TIME, mStore); 984 if (mColumnMode == COLUMN_MODE_AUTO) { 985 col.addControlListener(listener); 986 } 987 988 col = TableHelper.createTableColumn(t, "", SWT.CENTER, 989 "D", //$NON-NLS-1$ 990 PREFS_LEVEL, mStore); 991 if (mColumnMode == COLUMN_MODE_AUTO) { 992 col.addControlListener(listener); 993 } 994 995 col = TableHelper.createTableColumn(t, "pid", SWT.LEFT, 996 "9999", //$NON-NLS-1$ 997 PREFS_PID, mStore); 998 if (mColumnMode == COLUMN_MODE_AUTO) { 999 col.addControlListener(listener); 1000 } 1001 1002 col = TableHelper.createTableColumn(t, "tag", SWT.LEFT, 1003 "abcdefgh", //$NON-NLS-1$ 1004 PREFS_TAG, mStore); 1005 if (mColumnMode == COLUMN_MODE_AUTO) { 1006 col.addControlListener(listener); 1007 } 1008 1009 col = TableHelper.createTableColumn(t, "Message", SWT.LEFT, 1010 "abcdefghijklmnopqrstuvwxyz0123456789", //$NON-NLS-1$ 1011 PREFS_MESSAGE, mStore); 1012 if (mColumnMode == COLUMN_MODE_AUTO) { 1013 // instead of listening on resize for the last column, we make 1014 // it non resizable. 1015 col.setResizable(false); 1016 } 1017 1018 if (fillTable) { 1019 initFilter(filter); 1020 } 1021 return item; 1022 } 1023 } 1024 updateColumns(Table table)1025 protected void updateColumns(Table table) { 1026 if (table != null) { 1027 int index = 0; 1028 TableColumn col; 1029 1030 col = table.getColumn(index++); 1031 col.setWidth(mStore.getInt(PREFS_TIME)); 1032 1033 col = table.getColumn(index++); 1034 col.setWidth(mStore.getInt(PREFS_LEVEL)); 1035 1036 col = table.getColumn(index++); 1037 col.setWidth(mStore.getInt(PREFS_PID)); 1038 1039 col = table.getColumn(index++); 1040 col.setWidth(mStore.getInt(PREFS_TAG)); 1041 1042 col = table.getColumn(index++); 1043 col.setWidth(mStore.getInt(PREFS_MESSAGE)); 1044 } 1045 } 1046 resetUI(boolean inUiThread)1047 public void resetUI(boolean inUiThread) { 1048 if (mFilterMode == FILTER_AUTO_PID || mFilterMode == FILTER_AUTO_TAG) { 1049 if (inUiThread) { 1050 mFolders.dispose(); 1051 mParent.pack(true); 1052 createControl(mParent); 1053 } else { 1054 Display d = mFolders.getDisplay(); 1055 1056 // run sync as we need to update right now. 1057 d.syncExec(new Runnable() { 1058 @Override 1059 public void run() { 1060 mFolders.dispose(); 1061 mParent.pack(true); 1062 createControl(mParent); 1063 } 1064 }); 1065 } 1066 } else { 1067 // the ui is static we just empty it. 1068 if (mFolders.isDisposed() == false) { 1069 if (inUiThread) { 1070 emptyTables(); 1071 } else { 1072 Display d = mFolders.getDisplay(); 1073 1074 // run sync as we need to update right now. 1075 d.syncExec(new Runnable() { 1076 @Override 1077 public void run() { 1078 if (mFolders.isDisposed() == false) { 1079 emptyTables(); 1080 } 1081 } 1082 }); 1083 } 1084 } 1085 } 1086 } 1087 1088 /** 1089 * Process new Log lines coming from {@link LogCatOuputReceiver}. 1090 * @param lines the new lines 1091 */ processLogLines(String[] lines)1092 protected void processLogLines(String[] lines) { 1093 // WARNING: this will not work if the string contains more line than 1094 // the buffer holds. 1095 1096 if (lines.length > STRING_BUFFER_LENGTH) { 1097 Log.e("LogCat", "Receiving more lines than STRING_BUFFER_LENGTH"); 1098 } 1099 1100 // parse the lines and create LogMessage that are stored in a temporary list 1101 final ArrayList<LogMessage> newMessages = new ArrayList<LogMessage>(); 1102 1103 synchronized (mBuffer) { 1104 for (String line : lines) { 1105 // ignore empty lines. 1106 if (line.length() > 0) { 1107 // check for header lines. 1108 Matcher matcher = sLogPattern.matcher(line); 1109 if (matcher.matches()) { 1110 // this is a header line, parse the header and keep it around. 1111 mLastMessageInfo = new LogMessageInfo(); 1112 1113 mLastMessageInfo.time = matcher.group(1); 1114 mLastMessageInfo.pidString = matcher.group(2); 1115 mLastMessageInfo.pid = Integer.valueOf(mLastMessageInfo.pidString); 1116 mLastMessageInfo.logLevel = LogLevel.getByLetterString(matcher.group(4)); 1117 mLastMessageInfo.tag = matcher.group(5).trim(); 1118 } else { 1119 // This is not a header line. 1120 // Create a new LogMessage and process it. 1121 LogMessage mc = new LogMessage(); 1122 1123 if (mLastMessageInfo == null) { 1124 // The first line of output wasn't preceded 1125 // by a header line; make something up so 1126 // that users of mc.data don't NPE. 1127 mLastMessageInfo = new LogMessageInfo(); 1128 mLastMessageInfo.time = "??-?? ??:??:??.???"; //$NON-NLS1$ 1129 mLastMessageInfo.pidString = "<unknown>"; //$NON-NLS1$ 1130 mLastMessageInfo.pid = 0; 1131 mLastMessageInfo.logLevel = LogLevel.INFO; 1132 mLastMessageInfo.tag = "<unknown>"; //$NON-NLS1$ 1133 } 1134 1135 // If someone printed a log message with 1136 // embedded '\n' characters, there will 1137 // one header line followed by multiple text lines. 1138 // Use the last header that we saw. 1139 mc.data = mLastMessageInfo; 1140 1141 // tabs seem to display as only 1 tab so we replace the leading tabs 1142 // by 4 spaces. 1143 mc.msg = line.replaceAll("\t", " "); //$NON-NLS-1$ //$NON-NLS-2$ 1144 1145 // process the new LogMessage. 1146 processNewMessage(mc); 1147 1148 // store the new LogMessage 1149 newMessages.add(mc); 1150 } 1151 } 1152 } 1153 1154 // if we don't have a pending Runnable that will do the refresh, we ask the Display 1155 // to run one in the UI thread. 1156 if (mPendingAsyncRefresh == false) { 1157 mPendingAsyncRefresh = true; 1158 1159 try { 1160 Display display = mFolders.getDisplay(); 1161 1162 // run in sync because this will update the buffer start/end indices 1163 display.asyncExec(new Runnable() { 1164 @Override 1165 public void run() { 1166 asyncRefresh(); 1167 } 1168 }); 1169 } catch (SWTException e) { 1170 // display is disposed, we're probably quitting. Let's stop. 1171 stopLogCat(false); 1172 } 1173 } 1174 } 1175 } 1176 1177 /** 1178 * Refreshes the UI with new messages. 1179 */ asyncRefresh()1180 private void asyncRefresh() { 1181 if (mFolders.isDisposed() == false) { 1182 synchronized (mBuffer) { 1183 try { 1184 // the circular buffer has been updated, let have the filter flush their 1185 // display with the new messages. 1186 if (mFilters != null) { 1187 for (LogFilter f : mFilters) { 1188 f.flush(); 1189 } 1190 } 1191 1192 if (mDefaultFilter != null) { 1193 mDefaultFilter.flush(); 1194 } 1195 } finally { 1196 // the pending refresh is done. 1197 mPendingAsyncRefresh = false; 1198 } 1199 } 1200 } else { 1201 stopLogCat(true); 1202 } 1203 } 1204 1205 /** 1206 * Processes a new Message. 1207 * <p/>This adds the new message to the buffer, and gives it to the existing filters. 1208 * @param newMessage 1209 */ processNewMessage(LogMessage newMessage)1210 private void processNewMessage(LogMessage newMessage) { 1211 // if we are in auto filtering mode, make sure we have 1212 // a filter for this 1213 if (mFilterMode == FILTER_AUTO_PID || 1214 mFilterMode == FILTER_AUTO_TAG) { 1215 checkFilter(newMessage.data); 1216 } 1217 1218 // compute the index where the message goes. 1219 // was the buffer empty? 1220 int messageIndex = -1; 1221 if (mBufferStart == -1) { 1222 messageIndex = mBufferStart = 0; 1223 mBufferEnd = 1; 1224 } else { 1225 messageIndex = mBufferEnd; 1226 1227 // increment the next usable slot index 1228 mBufferEnd = (mBufferEnd + 1) % STRING_BUFFER_LENGTH; 1229 1230 // check we aren't overwriting start 1231 if (mBufferEnd == mBufferStart) { 1232 mBufferStart = (mBufferStart + 1) % STRING_BUFFER_LENGTH; 1233 } 1234 } 1235 1236 LogMessage oldMessage = null; 1237 1238 // record the message that was there before 1239 if (mBuffer[messageIndex] != null) { 1240 oldMessage = mBuffer[messageIndex]; 1241 } 1242 1243 // then add the new one 1244 mBuffer[messageIndex] = newMessage; 1245 1246 // give the new message to every filters. 1247 boolean filtered = false; 1248 if (mFilters != null) { 1249 for (LogFilter f : mFilters) { 1250 filtered |= f.addMessage(newMessage, oldMessage); 1251 } 1252 } 1253 if (filtered == false && mDefaultFilter != null) { 1254 mDefaultFilter.addMessage(newMessage, oldMessage); 1255 } 1256 } 1257 createFilters()1258 private void createFilters() { 1259 if (mFilterMode == FILTER_DEBUG || mFilterMode == FILTER_MANUAL) { 1260 // unarchive the filters. 1261 mFilters = mFilterStorage.getFilterFromStore(); 1262 1263 // set the colors 1264 if (mFilters != null) { 1265 for (LogFilter f : mFilters) { 1266 f.setColors(mColors); 1267 } 1268 } 1269 1270 if (mFilterStorage.requiresDefaultFilter()) { 1271 mDefaultFilter = new LogFilter("Log"); 1272 mDefaultFilter.setColors(mColors); 1273 mDefaultFilter.setSupportsDelete(false); 1274 mDefaultFilter.setSupportsEdit(false); 1275 } 1276 } else if (mFilterMode == FILTER_NONE) { 1277 // if the filtering mode is "none", we create a single filter that 1278 // will receive all 1279 mDefaultFilter = new LogFilter("Log"); 1280 mDefaultFilter.setColors(mColors); 1281 mDefaultFilter.setSupportsDelete(false); 1282 mDefaultFilter.setSupportsEdit(false); 1283 } 1284 } 1285 1286 /** Checks if there's an automatic filter for this md and if not 1287 * adds the filter and the ui. 1288 * This must be called from the UI! 1289 * @param md 1290 * @return true if the filter existed already 1291 */ checkFilter(final LogMessageInfo md)1292 private boolean checkFilter(final LogMessageInfo md) { 1293 if (true) 1294 return true; 1295 // look for a filter that matches the pid 1296 if (mFilterMode == FILTER_AUTO_PID) { 1297 for (LogFilter f : mFilters) { 1298 if (f.getPidFilter() == md.pid) { 1299 return true; 1300 } 1301 } 1302 } else if (mFilterMode == FILTER_AUTO_TAG) { 1303 for (LogFilter f : mFilters) { 1304 if (f.getTagFilter().equals(md.tag)) { 1305 return true; 1306 } 1307 } 1308 } 1309 1310 // if we reach this point, no filter was found. 1311 // create a filter with a temporary name of the pid 1312 final LogFilter newFilter = new LogFilter(md.pidString); 1313 String name = null; 1314 if (mFilterMode == FILTER_AUTO_PID) { 1315 newFilter.setPidMode(md.pid); 1316 1317 // ask the monitor thread if it knows the pid. 1318 name = mCurrentLoggedDevice.getClientName(md.pid); 1319 } else { 1320 newFilter.setTagMode(md.tag); 1321 name = md.tag; 1322 } 1323 addFilterToArray(newFilter); 1324 1325 final String fname = name; 1326 1327 // create the tabitem 1328 final TabItem newTabItem = createTab(newFilter, -1, true); 1329 1330 // if the name is unknown 1331 if (fname == null) { 1332 // we need to find the process running under that pid. 1333 // launch a thread do a ps on the device 1334 new Thread("remote PS") { //$NON-NLS-1$ 1335 @Override 1336 public void run() { 1337 // create the receiver 1338 PsOutputReceiver psor = new PsOutputReceiver(md.pid, 1339 newFilter, newTabItem); 1340 1341 // execute ps 1342 try { 1343 mCurrentLoggedDevice.executeShellCommand("ps", psor); //$NON-NLS-1$ 1344 } catch (IOException e) { 1345 // Ignore 1346 } catch (TimeoutException e) { 1347 // Ignore 1348 } catch (AdbCommandRejectedException e) { 1349 // Ignore 1350 } catch (ShellCommandUnresponsiveException e) { 1351 // Ignore 1352 } 1353 } 1354 }.start(); 1355 } 1356 1357 return false; 1358 } 1359 1360 /** 1361 * Adds a new filter to the current filter array, and set its colors 1362 * @param newFilter The filter to add 1363 */ addFilterToArray(LogFilter newFilter)1364 private void addFilterToArray(LogFilter newFilter) { 1365 // set the colors 1366 newFilter.setColors(mColors); 1367 1368 // add it to the array. 1369 if (mFilters != null && mFilters.length > 0) { 1370 LogFilter[] newFilters = new LogFilter[mFilters.length+1]; 1371 System.arraycopy(mFilters, 0, newFilters, 0, mFilters.length); 1372 newFilters[mFilters.length] = newFilter; 1373 mFilters = newFilters; 1374 } else { 1375 mFilters = new LogFilter[1]; 1376 mFilters[0] = newFilter; 1377 } 1378 } 1379 removeFilterFromArray(LogFilter oldFilter)1380 private void removeFilterFromArray(LogFilter oldFilter) { 1381 // look for the index 1382 int index = -1; 1383 for (int i = 0 ; i < mFilters.length ; i++) { 1384 if (mFilters[i] == oldFilter) { 1385 index = i; 1386 break; 1387 } 1388 } 1389 1390 if (index != -1) { 1391 LogFilter[] newFilters = new LogFilter[mFilters.length-1]; 1392 System.arraycopy(mFilters, 0, newFilters, 0, index); 1393 System.arraycopy(mFilters, index + 1, newFilters, index, 1394 newFilters.length-index); 1395 mFilters = newFilters; 1396 } 1397 } 1398 1399 /** 1400 * Initialize the filter with already existing buffer. 1401 * @param filter 1402 */ initFilter(LogFilter filter)1403 private void initFilter(LogFilter filter) { 1404 // is it empty 1405 if (filter.uiReady() == false) { 1406 return; 1407 } 1408 1409 if (filter == mDefaultFilter) { 1410 initDefaultFilter(); 1411 return; 1412 } 1413 1414 filter.clear(); 1415 1416 if (mBufferStart != -1) { 1417 int max = mBufferEnd; 1418 if (mBufferEnd < mBufferStart) { 1419 max += STRING_BUFFER_LENGTH; 1420 } 1421 1422 for (int i = mBufferStart; i < max; i++) { 1423 int realItemIndex = i % STRING_BUFFER_LENGTH; 1424 1425 filter.addMessage(mBuffer[realItemIndex], null /* old message */); 1426 } 1427 } 1428 1429 filter.flush(); 1430 filter.resetTempFilteringStatus(); 1431 } 1432 1433 /** 1434 * Refill the default filter. Not to be called directly. 1435 * @see initFilter() 1436 */ initDefaultFilter()1437 private void initDefaultFilter() { 1438 mDefaultFilter.clear(); 1439 1440 if (mBufferStart != -1) { 1441 int max = mBufferEnd; 1442 if (mBufferEnd < mBufferStart) { 1443 max += STRING_BUFFER_LENGTH; 1444 } 1445 1446 for (int i = mBufferStart; i < max; i++) { 1447 int realItemIndex = i % STRING_BUFFER_LENGTH; 1448 LogMessage msg = mBuffer[realItemIndex]; 1449 1450 // first we check that the other filters don't take this message 1451 boolean filtered = false; 1452 for (LogFilter f : mFilters) { 1453 filtered |= f.accept(msg); 1454 } 1455 1456 if (filtered == false) { 1457 mDefaultFilter.addMessage(msg, null /* old message */); 1458 } 1459 } 1460 } 1461 1462 mDefaultFilter.flush(); 1463 mDefaultFilter.resetTempFilteringStatus(); 1464 } 1465 1466 /** 1467 * Reset the filters, to handle change in device in automatic filter mode 1468 */ resetFilters()1469 private void resetFilters() { 1470 // if we are in automatic mode, then we need to rmove the current 1471 // filter. 1472 if (mFilterMode == FILTER_AUTO_PID || mFilterMode == FILTER_AUTO_TAG) { 1473 mFilters = null; 1474 1475 // recreate the filters. 1476 createFilters(); 1477 } 1478 } 1479 1480 getCurrentFilter()1481 private LogFilter getCurrentFilter() { 1482 int index = mFolders.getSelectionIndex(); 1483 1484 // if mFilters is null or index is invalid, we return the default 1485 // filter. It doesn't matter if that one is null as well, since we 1486 // would return null anyway. 1487 if (index == 0 || mFilters == null) { 1488 return mDefaultFilter; 1489 } 1490 1491 return mFilters[index-1]; 1492 } 1493 1494 emptyTables()1495 private void emptyTables() { 1496 for (LogFilter f : mFilters) { 1497 f.getTable().removeAll(); 1498 } 1499 1500 if (mDefaultFilter != null) { 1501 mDefaultFilter.getTable().removeAll(); 1502 } 1503 } 1504 updateFilteringWith(String text)1505 protected void updateFilteringWith(String text) { 1506 synchronized (mBuffer) { 1507 // reset the temp filtering for all the filters 1508 for (LogFilter f : mFilters) { 1509 f.resetTempFiltering(); 1510 } 1511 if (mDefaultFilter != null) { 1512 mDefaultFilter.resetTempFiltering(); 1513 } 1514 1515 // now we need to figure out the new temp filtering 1516 // split each word 1517 String[] segments = text.split(" "); //$NON-NLS-1$ 1518 1519 ArrayList<String> keywords = new ArrayList<String>(segments.length); 1520 1521 // loop and look for temp id/tag 1522 int tempPid = -1; 1523 String tempTag = null; 1524 for (int i = 0 ; i < segments.length; i++) { 1525 String s = segments[i]; 1526 if (tempPid == -1 && s.startsWith("pid:")) { //$NON-NLS-1$ 1527 // get the pid 1528 String[] seg = s.split(":"); //$NON-NLS-1$ 1529 if (seg.length == 2) { 1530 if (seg[1].matches("^[0-9]*$")) { //$NON-NLS-1$ 1531 tempPid = Integer.valueOf(seg[1]); 1532 } 1533 } 1534 } else if (tempTag == null && s.startsWith("tag:")) { //$NON-NLS-1$ 1535 String seg[] = segments[i].split(":"); //$NON-NLS-1$ 1536 if (seg.length == 2) { 1537 tempTag = seg[1]; 1538 } 1539 } else { 1540 keywords.add(s); 1541 } 1542 } 1543 1544 // set the temp filtering in the filters 1545 if (tempPid != -1 || tempTag != null || keywords.size() > 0) { 1546 String[] keywordsArray = keywords.toArray( 1547 new String[keywords.size()]); 1548 1549 for (LogFilter f : mFilters) { 1550 if (tempPid != -1) { 1551 f.setTempPidFiltering(tempPid); 1552 } 1553 if (tempTag != null) { 1554 f.setTempTagFiltering(tempTag); 1555 } 1556 f.setTempKeywordFiltering(keywordsArray); 1557 } 1558 1559 if (mDefaultFilter != null) { 1560 if (tempPid != -1) { 1561 mDefaultFilter.setTempPidFiltering(tempPid); 1562 } 1563 if (tempTag != null) { 1564 mDefaultFilter.setTempTagFiltering(tempTag); 1565 } 1566 mDefaultFilter.setTempKeywordFiltering(keywordsArray); 1567 1568 } 1569 } 1570 1571 initFilter(mCurrentFilter); 1572 } 1573 } 1574 1575 /** 1576 * Called when the current filter selection changes. 1577 * @param selectedFilter 1578 */ selectionChanged(LogFilter selectedFilter)1579 private void selectionChanged(LogFilter selectedFilter) { 1580 if (mLogLevelActions != null) { 1581 // get the log level 1582 int level = selectedFilter.getLogLevel(); 1583 for (int i = 0 ; i < mLogLevelActions.length; i++) { 1584 ICommonAction a = mLogLevelActions[i]; 1585 if (i == level - 2) { 1586 a.setChecked(true); 1587 } else { 1588 a.setChecked(false); 1589 } 1590 } 1591 } 1592 1593 if (mDeleteFilterAction != null) { 1594 mDeleteFilterAction.setEnabled(selectedFilter.supportsDelete()); 1595 } 1596 if (mEditFilterAction != null) { 1597 mEditFilterAction.setEnabled(selectedFilter.supportsEdit()); 1598 } 1599 } 1600 getSelectedErrorLineMessage()1601 public String getSelectedErrorLineMessage() { 1602 Table table = mCurrentFilter.getTable(); 1603 int[] selection = table.getSelectionIndices(); 1604 1605 if (selection.length == 1) { 1606 TableItem item = table.getItem(selection[0]); 1607 LogMessage msg = (LogMessage)item.getData(); 1608 if (msg.data.logLevel == LogLevel.ERROR || msg.data.logLevel == LogLevel.WARN) 1609 return msg.msg; 1610 } 1611 return null; 1612 } 1613 setLogCatViewInterface(LogCatViewInterface i)1614 public void setLogCatViewInterface(LogCatViewInterface i) { 1615 mLogCatViewInterface = i; 1616 } 1617 } 1618