OSDN Git Service

original
[gb-231r1-is01/Gingerbread_2.3.3_r1_IS01.git] / sdk / ddms / libs / ddmuilib / src / com / android / ddmuilib / log / event / EventLogPanel.java
1 /*
2  * Copyright (C) 2008 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.log.event;
18
19 import com.android.ddmlib.Client;
20 import com.android.ddmlib.IDevice;
21 import com.android.ddmlib.Log;
22 import com.android.ddmlib.Log.LogLevel;
23 import com.android.ddmlib.log.EventContainer;
24 import com.android.ddmlib.log.EventLogParser;
25 import com.android.ddmlib.log.LogReceiver;
26 import com.android.ddmlib.log.LogReceiver.ILogListener;
27 import com.android.ddmlib.log.LogReceiver.LogEntry;
28 import com.android.ddmuilib.DdmUiPreferences;
29 import com.android.ddmuilib.TablePanel;
30 import com.android.ddmuilib.actions.ICommonAction;
31 import com.android.ddmuilib.annotation.UiThread;
32 import com.android.ddmuilib.annotation.WorkerThread;
33 import com.android.ddmuilib.log.event.EventDisplay.ILogColumnListener;
34
35 import org.eclipse.jface.preference.IPreferenceStore;
36 import org.eclipse.swt.SWT;
37 import org.eclipse.swt.SWTException;
38 import org.eclipse.swt.custom.ScrolledComposite;
39 import org.eclipse.swt.events.ControlAdapter;
40 import org.eclipse.swt.events.ControlEvent;
41 import org.eclipse.swt.events.DisposeEvent;
42 import org.eclipse.swt.events.DisposeListener;
43 import org.eclipse.swt.graphics.Rectangle;
44 import org.eclipse.swt.layout.GridData;
45 import org.eclipse.swt.layout.RowData;
46 import org.eclipse.swt.layout.RowLayout;
47 import org.eclipse.swt.widgets.Composite;
48 import org.eclipse.swt.widgets.Control;
49 import org.eclipse.swt.widgets.Display;
50 import org.eclipse.swt.widgets.FileDialog;
51 import org.eclipse.swt.widgets.Table;
52 import org.eclipse.swt.widgets.TableColumn;
53
54 import java.io.File;
55 import java.io.FileInputStream;
56 import java.io.FileNotFoundException;
57 import java.io.FileOutputStream;
58 import java.io.IOException;
59 import java.text.NumberFormat;
60 import java.util.ArrayList;
61 import java.util.regex.Pattern;
62
63 /**
64  * Event log viewer
65  */
66 public class EventLogPanel extends TablePanel implements ILogListener,
67         ILogColumnListener {
68
69     private final static String TAG_FILE_EXT = ".tag"; //$NON-NLS-1$
70
71     private final static String PREFS_EVENT_DISPLAY = "EventLogPanel.eventDisplay"; //$NON-NLS-1$
72     private final static String EVENT_DISPLAY_STORAGE_SEPARATOR = "|"; //$NON-NLS-1$
73
74     static final String PREFS_DISPLAY_WIDTH = "EventLogPanel.width"; //$NON-NLS-1$
75     static final String PREFS_DISPLAY_HEIGHT = "EventLogPanel.height"; //$NON-NLS-1$
76
77     private final static int DEFAULT_DISPLAY_WIDTH = 500;
78     private final static int DEFAULT_DISPLAY_HEIGHT = 400;
79
80     private IDevice mCurrentLoggedDevice;
81     private String mCurrentLogFile;
82     private LogReceiver mCurrentLogReceiver;
83     private EventLogParser mCurrentEventLogParser;
84
85     private Object mLock = new Object();
86
87     /** list of all the events. */
88     private final ArrayList<EventContainer> mEvents = new ArrayList<EventContainer>();
89
90     /** list of all the new events, that have yet to be displayed by the ui */
91     private final ArrayList<EventContainer> mNewEvents = new ArrayList<EventContainer>();
92     /** indicates a pending ui thread display */
93     private boolean mPendingDisplay = false;
94
95     /** list of all the custom event displays */
96     private final ArrayList<EventDisplay> mEventDisplays = new ArrayList<EventDisplay>();
97
98     private final NumberFormat mFormatter = NumberFormat.getInstance();
99     private Composite mParent;
100     private ScrolledComposite mBottomParentPanel;
101     private Composite mBottomPanel;
102     private ICommonAction mOptionsAction;
103     private ICommonAction mClearAction;
104     private ICommonAction mSaveAction;
105     private ICommonAction mLoadAction;
106     private ICommonAction mImportAction;
107
108     /** file containing the current log raw data. */
109     private File mTempFile = null;
110
111     public EventLogPanel() {
112         super();
113         mFormatter.setGroupingUsed(true);
114     }
115
116     /**
117      * Sets the external actions.
118      * <p/>This method sets up the {@link ICommonAction} objects to execute the proper code
119      * when triggered by using {@link ICommonAction#setRunnable(Runnable)}.
120      * <p/>It will also make sure they are enabled only when possible.
121      * @param optionsAction
122      * @param clearAction
123      * @param saveAction
124      * @param loadAction
125      * @param importAction
126      */
127     public void setActions(ICommonAction optionsAction, ICommonAction clearAction,
128             ICommonAction saveAction, ICommonAction loadAction, ICommonAction importAction) {
129         mOptionsAction = optionsAction;
130         mOptionsAction.setRunnable(new Runnable() {
131             public void run() {
132                 openOptionPanel();
133             }
134         });
135
136         mClearAction = clearAction;
137         mClearAction.setRunnable(new Runnable() {
138             public void run() {
139                 clearLog();
140             }
141         });
142
143         mSaveAction = saveAction;
144         mSaveAction.setRunnable(new Runnable() {
145             public void run() {
146                 try {
147                     FileDialog fileDialog = new FileDialog(mParent.getShell(), SWT.SAVE);
148
149                     fileDialog.setText("Save Event Log");
150                     fileDialog.setFileName("event.log");
151
152                     String fileName = fileDialog.open();
153                     if (fileName != null) {
154                         saveLog(fileName);
155                     }
156                 } catch (IOException e1) {
157                 }
158             }
159         });
160
161         mLoadAction = loadAction;
162         mLoadAction.setRunnable(new Runnable() {
163             public void run() {
164                 FileDialog fileDialog = new FileDialog(mParent.getShell(), SWT.OPEN);
165
166                 fileDialog.setText("Load Event Log");
167
168                 String fileName = fileDialog.open();
169                 if (fileName != null) {
170                     loadLog(fileName);
171                 }
172             }
173         });
174
175         mImportAction = importAction;
176         mImportAction.setRunnable(new Runnable() {
177             public void run() {
178                 FileDialog fileDialog = new FileDialog(mParent.getShell(), SWT.OPEN);
179
180                 fileDialog.setText("Import Bug Report");
181
182                 String fileName = fileDialog.open();
183                 if (fileName != null) {
184                     importBugReport(fileName);
185                 }
186             }
187         });
188
189         mOptionsAction.setEnabled(false);
190         mClearAction.setEnabled(false);
191         mSaveAction.setEnabled(false);
192     }
193
194     /**
195      * Opens the option panel.
196      * </p>
197      * <b>This must be called from the UI thread</b>
198      */
199     @UiThread
200     public void openOptionPanel() {
201         try {
202             EventDisplayOptions dialog = new EventDisplayOptions(mParent.getShell());
203             if (dialog.open(mCurrentEventLogParser, mEventDisplays, mEvents)) {
204                 synchronized (mLock) {
205                     // get the new EventDisplay list
206                     mEventDisplays.clear();
207                     mEventDisplays.addAll(dialog.getEventDisplays());
208
209                     // since the list of EventDisplay changed, we store it.
210                     saveEventDisplays();
211
212                     rebuildUi();
213                 }
214             }
215         } catch (SWTException e) {
216             Log.e("EventLog", e); //$NON-NLS-1$
217         }
218     }
219
220     /**
221      * Clears the log.
222      * <p/>
223      * <b>This must be called from the UI thread</b>
224      */
225     public void clearLog() {
226         try {
227             synchronized (mLock) {
228                 mEvents.clear();
229                 mNewEvents.clear();
230                 mPendingDisplay = false;
231                 for (EventDisplay eventDisplay : mEventDisplays) {
232                     eventDisplay.resetUI();
233                 }
234             }
235         } catch (SWTException e) {
236             Log.e("EventLog", e); //$NON-NLS-1$
237         }
238     }
239
240     /**
241      * Saves the content of the event log into a file. The log is saved in the same
242      * binary format than on the device.
243      * @param filePath
244      * @throws IOException
245      */
246     public void saveLog(String filePath) throws IOException {
247         if (mCurrentLoggedDevice != null && mCurrentEventLogParser != null) {
248             File destFile = new File(filePath);
249             destFile.createNewFile();
250             FileInputStream fis = new FileInputStream(mTempFile);
251             FileOutputStream fos = new FileOutputStream(destFile);
252             byte[] buffer = new byte[1024];
253
254             int count;
255
256             while ((count = fis.read(buffer)) != -1) {
257                 fos.write(buffer, 0, count);
258             }
259
260             fos.close();
261             fis.close();
262
263             // now we save the tag file
264             filePath = filePath + TAG_FILE_EXT;
265             mCurrentEventLogParser.saveTags(filePath);
266         }
267     }
268
269     /**
270      * Loads a binary event log (if has associated .tag file) or
271      * otherwise loads a textual event log.
272      * @param filePath Event log path (and base of potential tag file)
273      */
274     public void loadLog(String filePath) {
275         if ((new File(filePath + TAG_FILE_EXT)).exists()) {
276             startEventLogFromFiles(filePath);
277         } else {
278             try {
279                 EventLogImporter importer = new EventLogImporter(filePath);
280                 String[] tags = importer.getTags();
281                 String[] log = importer.getLog();
282                 startEventLogFromContent(tags, log);
283             } catch (FileNotFoundException e) {
284                 // If this fails, display the error message from startEventLogFromFiles,
285                 // and pretend we never tried EventLogImporter
286                 Log.logAndDisplay(Log.LogLevel.ERROR, "EventLog",
287                         String.format("Failure to read %1$s", filePath + TAG_FILE_EXT));
288             }
289
290         }
291     }
292
293     public void importBugReport(String filePath) {
294         try {
295             BugReportImporter importer = new BugReportImporter(filePath);
296
297             String[] tags = importer.getTags();
298             String[] log = importer.getLog();
299
300             startEventLogFromContent(tags, log);
301
302         } catch (FileNotFoundException e) {
303             Log.logAndDisplay(LogLevel.ERROR, "Import",
304                     "Unable to import bug report: " + e.getMessage());
305         }
306     }
307
308     /* (non-Javadoc)
309      * @see com.android.ddmuilib.SelectionDependentPanel#clientSelected()
310      */
311     @Override
312     public void clientSelected() {
313         // pass
314     }
315
316     /* (non-Javadoc)
317      * @see com.android.ddmuilib.SelectionDependentPanel#deviceSelected()
318      */
319     @Override
320     public void deviceSelected() {
321         startEventLog(getCurrentDevice());
322     }
323
324     /*
325      * (non-Javadoc)
326      * @see com.android.ddmlib.AndroidDebugBridge.IClientChangeListener#clientChanged(com.android.ddmlib.Client, int)
327      */
328     public void clientChanged(Client client, int changeMask) {
329         // pass
330     }
331
332     /* (non-Javadoc)
333      * @see com.android.ddmuilib.Panel#createControl(org.eclipse.swt.widgets.Composite)
334      */
335     @Override
336     protected Control createControl(Composite parent) {
337         mParent = parent;
338         mParent.addDisposeListener(new DisposeListener() {
339             public void widgetDisposed(DisposeEvent e) {
340                 synchronized (mLock) {
341                     if (mCurrentLogReceiver != null) {
342                         mCurrentLogReceiver.cancel();
343                         mCurrentLogReceiver = null;
344                         mCurrentEventLogParser = null;
345                         mCurrentLoggedDevice = null;
346                         mEventDisplays.clear();
347                         mEvents.clear();
348                     }
349                 }
350             }
351         });
352
353         final IPreferenceStore store = DdmUiPreferences.getStore();
354
355         // init some store stuff
356         store.setDefault(PREFS_DISPLAY_WIDTH, DEFAULT_DISPLAY_WIDTH);
357         store.setDefault(PREFS_DISPLAY_HEIGHT, DEFAULT_DISPLAY_HEIGHT);
358
359         mBottomParentPanel = new ScrolledComposite(parent, SWT.V_SCROLL);
360         mBottomParentPanel.setLayoutData(new GridData(GridData.FILL_BOTH));
361         mBottomParentPanel.setExpandHorizontal(true);
362         mBottomParentPanel.setExpandVertical(true);
363
364         mBottomParentPanel.addControlListener(new ControlAdapter() {
365             @Override
366             public void controlResized(ControlEvent e) {
367                 if (mBottomPanel != null) {
368                     Rectangle r = mBottomParentPanel.getClientArea();
369                     mBottomParentPanel.setMinSize(mBottomPanel.computeSize(r.width,
370                         SWT.DEFAULT));
371                 }
372             }
373         });
374
375         prepareDisplayUi();
376
377         // load the EventDisplay from storage.
378         loadEventDisplays();
379
380         // create the ui
381         createDisplayUi();
382
383         return mBottomParentPanel;
384     }
385
386     /* (non-Javadoc)
387      * @see com.android.ddmuilib.Panel#postCreation()
388      */
389     @Override
390     protected void postCreation() {
391         // pass
392     }
393
394     /* (non-Javadoc)
395      * @see com.android.ddmuilib.Panel#setFocus()
396      */
397     @Override
398     public void setFocus() {
399         mBottomParentPanel.setFocus();
400     }
401
402     /**
403      * Starts a new logcat and set mCurrentLogCat as the current receiver.
404      * @param device the device to connect logcat to.
405      */
406     private void startEventLog(final IDevice device) {
407         if (device == mCurrentLoggedDevice) {
408             return;
409         }
410
411         // if we have a logcat already running
412         if (mCurrentLogReceiver != null) {
413             stopEventLog(false);
414         }
415         mCurrentLoggedDevice = null;
416         mCurrentLogFile = null;
417
418         if (device != null) {
419             // create a new output receiver
420             mCurrentLogReceiver = new LogReceiver(this);
421
422             // start the logcat in a different thread
423             new Thread("EventLog")  { //$NON-NLS-1$
424                 @Override
425                 public void run() {
426                     while (device.isOnline() == false &&
427                             mCurrentLogReceiver != null &&
428                             mCurrentLogReceiver.isCancelled() == false) {
429                         try {
430                             sleep(2000);
431                         } catch (InterruptedException e) {
432                             return;
433                         }
434                     }
435
436                     if (mCurrentLogReceiver == null || mCurrentLogReceiver.isCancelled()) {
437                         // logcat was stopped/cancelled before the device became ready.
438                         return;
439                     }
440
441                     try {
442                         mCurrentLoggedDevice = device;
443                         synchronized (mLock) {
444                             mCurrentEventLogParser = new EventLogParser();
445                             mCurrentEventLogParser.init(device);
446                         }
447
448                         // update the event display with the new parser.
449                         updateEventDisplays();
450
451                         // prepare the temp file that will contain the raw data
452                         mTempFile = File.createTempFile("android-event-", ".log");
453
454                         device.runEventLogService(mCurrentLogReceiver);
455                     } catch (Exception e) {
456                         Log.e("EventLog", e);
457                     } finally {
458                     }
459                 }
460             }.start();
461         }
462     }
463
464     private void startEventLogFromFiles(final String fileName) {
465         // if we have a logcat already running
466         if (mCurrentLogReceiver != null) {
467             stopEventLog(false);
468         }
469         mCurrentLoggedDevice = null;
470         mCurrentLogFile = null;
471
472         // create a new output receiver
473         mCurrentLogReceiver = new LogReceiver(this);
474
475         mSaveAction.setEnabled(false);
476
477         // start the logcat in a different thread
478         new Thread("EventLog")  { //$NON-NLS-1$
479             @Override
480             public void run() {
481                 try {
482                     mCurrentLogFile = fileName;
483                     synchronized (mLock) {
484                         mCurrentEventLogParser = new EventLogParser();
485                         if (mCurrentEventLogParser.init(fileName + TAG_FILE_EXT) == false) {
486                             mCurrentEventLogParser = null;
487                             Log.logAndDisplay(LogLevel.ERROR, "EventLog",
488                                     String.format("Failure to read %1$s", fileName + TAG_FILE_EXT));
489                             return;
490                         }
491                     }
492
493                     // update the event display with the new parser.
494                     updateEventDisplays();
495
496                     runLocalEventLogService(fileName, mCurrentLogReceiver);
497                 } catch (Exception e) {
498                     Log.e("EventLog", e);
499                 } finally {
500                 }
501             }
502         }.start();
503     }
504
505     private void startEventLogFromContent(final String[] tags, final String[] log) {
506         // if we have a logcat already running
507         if (mCurrentLogReceiver != null) {
508             stopEventLog(false);
509         }
510         mCurrentLoggedDevice = null;
511         mCurrentLogFile = null;
512
513         // create a new output receiver
514         mCurrentLogReceiver = new LogReceiver(this);
515
516         mSaveAction.setEnabled(false);
517
518         // start the logcat in a different thread
519         new Thread("EventLog")  { //$NON-NLS-1$
520             @Override
521             public void run() {
522                 try {
523                     synchronized (mLock) {
524                         mCurrentEventLogParser = new EventLogParser();
525                         if (mCurrentEventLogParser.init(tags) == false) {
526                             mCurrentEventLogParser = null;
527                             return;
528                         }
529                     }
530
531                     // update the event display with the new parser.
532                     updateEventDisplays();
533
534                     runLocalEventLogService(log, mCurrentLogReceiver);
535                 } catch (Exception e) {
536                     Log.e("EventLog", e);
537                 } finally {
538                 }
539             }
540         }.start();
541     }
542
543
544     public void stopEventLog(boolean inUiThread) {
545         if (mCurrentLogReceiver != null) {
546             mCurrentLogReceiver.cancel();
547
548             // when the thread finishes, no one will reference that object
549             // and it'll be destroyed
550             synchronized (mLock) {
551                 mCurrentLogReceiver = null;
552                 mCurrentEventLogParser = null;
553
554                 mCurrentLoggedDevice = null;
555                 mEvents.clear();
556                 mNewEvents.clear();
557                 mPendingDisplay = false;
558             }
559
560             resetUI(inUiThread);
561         }
562
563         if (mTempFile != null) {
564             mTempFile.delete();
565             mTempFile = null;
566         }
567     }
568
569     private void resetUI(boolean inUiThread) {
570         mEvents.clear();
571
572         // the ui is static we just empty it.
573         if (inUiThread) {
574             resetUiFromUiThread();
575         } else {
576             try {
577                 Display d = mBottomParentPanel.getDisplay();
578
579                 // run sync as we need to update right now.
580                 d.syncExec(new Runnable() {
581                     public void run() {
582                         if (mBottomParentPanel.isDisposed() == false) {
583                             resetUiFromUiThread();
584                         }
585                     }
586                 });
587             } catch (SWTException e) {
588                 // display is disposed, we're quitting. Do nothing.
589             }
590         }
591     }
592
593     private void resetUiFromUiThread() {
594         synchronized(mLock) {
595             for (EventDisplay eventDisplay : mEventDisplays) {
596                 eventDisplay.resetUI();
597             }
598         }
599         mOptionsAction.setEnabled(false);
600         mClearAction.setEnabled(false);
601         mSaveAction.setEnabled(false);
602     }
603
604     private void prepareDisplayUi() {
605         mBottomPanel = new Composite(mBottomParentPanel, SWT.NONE);
606         mBottomParentPanel.setContent(mBottomPanel);
607     }
608
609     private void createDisplayUi() {
610         RowLayout rowLayout = new RowLayout();
611         rowLayout.wrap = true;
612         rowLayout.pack = false;
613         rowLayout.justify = true;
614         rowLayout.fill = true;
615         rowLayout.type = SWT.HORIZONTAL;
616         mBottomPanel.setLayout(rowLayout);
617
618         IPreferenceStore store = DdmUiPreferences.getStore();
619         int displayWidth = store.getInt(PREFS_DISPLAY_WIDTH);
620         int displayHeight = store.getInt(PREFS_DISPLAY_HEIGHT);
621
622         for (EventDisplay eventDisplay : mEventDisplays) {
623             Control c = eventDisplay.createComposite(mBottomPanel, mCurrentEventLogParser, this);
624             if (c != null) {
625                 RowData rd = new RowData();
626                 rd.height = displayHeight;
627                 rd.width = displayWidth;
628                 c.setLayoutData(rd);
629             }
630
631             Table table = eventDisplay.getTable();
632             if (table != null) {
633                 addTableToFocusListener(table);
634             }
635         }
636
637         mBottomPanel.layout();
638         mBottomParentPanel.setMinSize(mBottomPanel.computeSize(SWT.DEFAULT, SWT.DEFAULT));
639         mBottomParentPanel.layout();
640     }
641
642     /**
643      * Rebuild the display ui.
644      */
645     @UiThread
646     private void rebuildUi() {
647         synchronized (mLock) {
648             // we need to rebuild the ui. First we get rid of it.
649             mBottomPanel.dispose();
650             mBottomPanel = null;
651
652             prepareDisplayUi();
653             createDisplayUi();
654
655             // and fill it
656
657             boolean start_event = false;
658             synchronized (mNewEvents) {
659                 mNewEvents.addAll(0, mEvents);
660
661                 if (mPendingDisplay == false) {
662                     mPendingDisplay = true;
663                     start_event = true;
664                 }
665             }
666
667             if (start_event) {
668                 scheduleUIEventHandler();
669             }
670
671             Rectangle r = mBottomParentPanel.getClientArea();
672             mBottomParentPanel.setMinSize(mBottomPanel.computeSize(r.width,
673                 SWT.DEFAULT));
674         }
675     }
676
677
678     /**
679      * Processes a new {@link LogEntry} by parsing it with {@link EventLogParser} and displaying it.
680      * @param entry The new log entry
681      * @see LogReceiver.ILogListener#newEntry(LogEntry)
682      */
683     @WorkerThread
684     public void newEntry(LogEntry entry) {
685         synchronized (mLock) {
686             if (mCurrentEventLogParser != null) {
687                 EventContainer event = mCurrentEventLogParser.parse(entry);
688                 if (event != null) {
689                     handleNewEvent(event);
690                 }
691             }
692         }
693     }
694
695     @WorkerThread
696     private void handleNewEvent(EventContainer event) {
697         // add the event to the generic list
698         mEvents.add(event);
699
700         // add to the list of events that needs to be displayed, and trigger a
701         // new display if needed.
702         boolean start_event = false;
703         synchronized (mNewEvents) {
704             mNewEvents.add(event);
705
706             if (mPendingDisplay == false) {
707                 mPendingDisplay = true;
708                 start_event = true;
709             }
710         }
711
712         if (start_event == false) {
713             // we're done
714             return;
715         }
716
717         scheduleUIEventHandler();
718     }
719
720     /**
721      * Schedules the UI thread to execute a {@link Runnable} calling {@link #displayNewEvents()}.
722      */
723     private void scheduleUIEventHandler() {
724         try  {
725             Display d = mBottomParentPanel.getDisplay();
726             d.asyncExec(new Runnable() {
727                 public void run() {
728                     if (mBottomParentPanel.isDisposed() == false) {
729                         if (mCurrentEventLogParser != null) {
730                             displayNewEvents();
731                         }
732                     }
733                 }
734             });
735         } catch (SWTException e) {
736             // if the ui is disposed, do nothing
737         }
738     }
739
740     /**
741      * Processes raw data coming from the log service.
742      * @see LogReceiver.ILogListener#newData(byte[], int, int)
743      */
744     public void newData(byte[] data, int offset, int length) {
745         if (mTempFile != null) {
746             try {
747                 FileOutputStream fos = new FileOutputStream(mTempFile, true /* append */);
748                 fos.write(data, offset, length);
749                 fos.close();
750             } catch (FileNotFoundException e) {
751             } catch (IOException e) {
752             }
753         }
754     }
755
756     @UiThread
757     private void displayNewEvents() {
758         // never display more than 1,000 events in this loop. We can't do too much in the UI thread.
759         int count = 0;
760
761         // prepare the displays
762         for (EventDisplay eventDisplay : mEventDisplays) {
763             eventDisplay.startMultiEventDisplay();
764         }
765
766         // display the new events
767         EventContainer event = null;
768         boolean need_to_reloop = false;
769         do {
770             // get the next event to display.
771             synchronized (mNewEvents) {
772                 if (mNewEvents.size() > 0) {
773                     if (count > 200) {
774                         // there are still events to be displayed, but we don't want to hog the
775                         // UI thread for too long, so we stop this runnable, but launch a new
776                         // one to keep going.
777                         need_to_reloop = true;
778                         event = null;
779                     } else {
780                         event = mNewEvents.remove(0);
781                         count++;
782                     }
783                 } else {
784                     // we're done.
785                     event = null;
786                     mPendingDisplay = false;
787                 }
788             }
789
790             if (event != null) {
791                 // notify the event display
792                 for (EventDisplay eventDisplay : mEventDisplays) {
793                     eventDisplay.newEvent(event, mCurrentEventLogParser);
794                 }
795             }
796         } while (event != null);
797
798         // we're done displaying events.
799         for (EventDisplay eventDisplay : mEventDisplays) {
800             eventDisplay.endMultiEventDisplay();
801         }
802
803         // if needed, ask the UI thread to re-run this method.
804         if (need_to_reloop) {
805             scheduleUIEventHandler();
806         }
807     }
808
809     /**
810      * Loads the {@link EventDisplay}s from the preference store.
811      */
812     private void loadEventDisplays() {
813         IPreferenceStore store = DdmUiPreferences.getStore();
814         String storage = store.getString(PREFS_EVENT_DISPLAY);
815
816         if (storage.length() > 0) {
817             String[] values = storage.split(Pattern.quote(EVENT_DISPLAY_STORAGE_SEPARATOR));
818
819             for (String value : values) {
820                 EventDisplay eventDisplay = EventDisplay.load(value);
821                 if (eventDisplay != null) {
822                     mEventDisplays.add(eventDisplay);
823                 }
824             }
825         }
826     }
827
828     /**
829      * Saves the {@link EventDisplay}s into the {@link DdmUiPreferences} store.
830      */
831     private void saveEventDisplays() {
832         IPreferenceStore store = DdmUiPreferences.getStore();
833
834         boolean first = true;
835         StringBuilder sb = new StringBuilder();
836
837         for (EventDisplay eventDisplay : mEventDisplays) {
838             String storage = eventDisplay.getStorageString();
839             if (storage != null) {
840                 if (first == false) {
841                     sb.append(EVENT_DISPLAY_STORAGE_SEPARATOR);
842                 } else {
843                     first = false;
844                 }
845
846                 sb.append(storage);
847             }
848         }
849
850         store.setValue(PREFS_EVENT_DISPLAY, sb.toString());
851     }
852
853     /**
854      * Updates the {@link EventDisplay} with the new {@link EventLogParser}.
855      * <p/>
856      * This will run asynchronously in the UI thread.
857      */
858     @WorkerThread
859     private void updateEventDisplays() {
860         try {
861             Display d = mBottomParentPanel.getDisplay();
862
863             d.asyncExec(new Runnable() {
864                 public void run() {
865                     if (mBottomParentPanel.isDisposed() == false) {
866                         for (EventDisplay eventDisplay : mEventDisplays) {
867                             eventDisplay.setNewLogParser(mCurrentEventLogParser);
868                         }
869
870                         mOptionsAction.setEnabled(true);
871                         mClearAction.setEnabled(true);
872                         if (mCurrentLogFile == null) {
873                             mSaveAction.setEnabled(true);
874                         } else {
875                             mSaveAction.setEnabled(false);
876                         }
877                     }
878                 }
879             });
880         } catch (SWTException e) {
881             // display is disposed: do nothing.
882         }
883     }
884
885     @UiThread
886     public void columnResized(int index, TableColumn sourceColumn) {
887         for (EventDisplay eventDisplay : mEventDisplays) {
888             eventDisplay.resizeColumn(index, sourceColumn);
889         }
890     }
891
892     /**
893      * Runs an event log service out of a local file.
894      * @param fileName the full file name of the local file containing the event log.
895      * @param logReceiver the receiver that will handle the log
896      * @throws IOException
897      */
898     @WorkerThread
899     private void runLocalEventLogService(String fileName, LogReceiver logReceiver)
900             throws IOException {
901         byte[] buffer = new byte[256];
902
903         FileInputStream fis = new FileInputStream(fileName);
904
905         int count;
906         while ((count = fis.read(buffer)) != -1) {
907             logReceiver.parseNewData(buffer, 0, count);
908         }
909     }
910
911     @WorkerThread
912     private void runLocalEventLogService(String[] log, LogReceiver currentLogReceiver) {
913         synchronized (mLock) {
914             for (String line : log) {
915                 EventContainer event = mCurrentEventLogParser.parse(line);
916                 if (event != null) {
917                     handleNewEvent(event);
918                 }
919             }
920         }
921     }
922 }