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