OSDN Git Service

android-2.1_r1 snapshot
[android-x86/sdk.git] / ddms / app / src / com / android / ddms / UIThread.java
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.ddms;
18
19 import com.android.ddmlib.AndroidDebugBridge;
20 import com.android.ddmlib.Client;
21 import com.android.ddmlib.ClientData;
22 import com.android.ddmlib.IDevice;
23 import com.android.ddmlib.Log;
24 import com.android.ddmlib.SyncService;
25 import com.android.ddmlib.AndroidDebugBridge.IClientChangeListener;
26 import com.android.ddmlib.ClientData.IHprofDumpHandler;
27 import com.android.ddmlib.ClientData.MethodProfilingStatus;
28 import com.android.ddmlib.Log.ILogOutput;
29 import com.android.ddmlib.Log.LogLevel;
30 import com.android.ddmlib.SyncService.SyncResult;
31 import com.android.ddmuilib.AllocationPanel;
32 import com.android.ddmuilib.DevicePanel;
33 import com.android.ddmuilib.EmulatorControlPanel;
34 import com.android.ddmuilib.HeapPanel;
35 import com.android.ddmuilib.ITableFocusListener;
36 import com.android.ddmuilib.ImageHelper;
37 import com.android.ddmuilib.ImageLoader;
38 import com.android.ddmuilib.InfoPanel;
39 import com.android.ddmuilib.NativeHeapPanel;
40 import com.android.ddmuilib.ScreenShotDialog;
41 import com.android.ddmuilib.SysinfoPanel;
42 import com.android.ddmuilib.TablePanel;
43 import com.android.ddmuilib.ThreadPanel;
44 import com.android.ddmuilib.DevicePanel.IUiSelectionListener;
45 import com.android.ddmuilib.actions.ToolItemAction;
46 import com.android.ddmuilib.explorer.DeviceExplorer;
47 import com.android.ddmuilib.handler.BaseFileHandler;
48 import com.android.ddmuilib.handler.MethodProfilingHandler;
49 import com.android.ddmuilib.log.event.EventLogPanel;
50 import com.android.ddmuilib.logcat.LogColors;
51 import com.android.ddmuilib.logcat.LogFilter;
52 import com.android.ddmuilib.logcat.LogPanel;
53 import com.android.ddmuilib.logcat.LogPanel.ILogFilterStorageManager;
54
55 import org.eclipse.jface.dialogs.MessageDialog;
56 import org.eclipse.jface.preference.IPreferenceStore;
57 import org.eclipse.jface.preference.PreferenceStore;
58 import org.eclipse.swt.SWT;
59 import org.eclipse.swt.SWTError;
60 import org.eclipse.swt.SWTException;
61 import org.eclipse.swt.dnd.Clipboard;
62 import org.eclipse.swt.events.ControlEvent;
63 import org.eclipse.swt.events.ControlListener;
64 import org.eclipse.swt.events.MenuAdapter;
65 import org.eclipse.swt.events.MenuEvent;
66 import org.eclipse.swt.events.SelectionAdapter;
67 import org.eclipse.swt.events.SelectionEvent;
68 import org.eclipse.swt.events.ShellEvent;
69 import org.eclipse.swt.events.ShellListener;
70 import org.eclipse.swt.graphics.Color;
71 import org.eclipse.swt.graphics.Font;
72 import org.eclipse.swt.graphics.FontData;
73 import org.eclipse.swt.graphics.Image;
74 import org.eclipse.swt.graphics.Rectangle;
75 import org.eclipse.swt.layout.FillLayout;
76 import org.eclipse.swt.layout.FormAttachment;
77 import org.eclipse.swt.layout.FormData;
78 import org.eclipse.swt.layout.FormLayout;
79 import org.eclipse.swt.layout.GridData;
80 import org.eclipse.swt.layout.GridLayout;
81 import org.eclipse.swt.widgets.Composite;
82 import org.eclipse.swt.widgets.Display;
83 import org.eclipse.swt.widgets.Event;
84 import org.eclipse.swt.widgets.Label;
85 import org.eclipse.swt.widgets.Listener;
86 import org.eclipse.swt.widgets.Menu;
87 import org.eclipse.swt.widgets.MenuItem;
88 import org.eclipse.swt.widgets.MessageBox;
89 import org.eclipse.swt.widgets.Sash;
90 import org.eclipse.swt.widgets.Shell;
91 import org.eclipse.swt.widgets.TabFolder;
92 import org.eclipse.swt.widgets.TabItem;
93 import org.eclipse.swt.widgets.ToolBar;
94 import org.eclipse.swt.widgets.ToolItem;
95
96 import java.io.File;
97 import java.util.ArrayList;
98
99 /**
100  * This acts as the UI builder. This cannot be its own thread since this prevent using AWT in an
101  * SWT application. So this class mainly builds the ui, and manages communication between the panels
102  * when {@link IDevice} / {@link Client} selection changes.
103  */
104 public class UIThread implements IUiSelectionListener, IClientChangeListener {
105     /*
106      * UI tab panel definitions. The constants here must match up with the array
107      * indices in mPanels. PANEL_CLIENT_LIST is a "virtual" panel representing
108      * the client list.
109      */
110     public static final int PANEL_CLIENT_LIST = -1;
111
112     public static final int PANEL_INFO = 0;
113
114     public static final int PANEL_THREAD = 1;
115
116     public static final int PANEL_HEAP = 2;
117
118     public static final int PANEL_NATIVE_HEAP = 3;
119
120     private static final int PANEL_ALLOCATIONS = 4;
121
122     private static final int PANEL_SYSINFO = 5;
123
124     private static final int PANEL_COUNT = 6;
125
126     /** Content is setup in the constructor */
127     private static TablePanel[] mPanels = new TablePanel[PANEL_COUNT];
128
129     private static final String[] mPanelNames = new String[] {
130             "Info", "Threads", "VM Heap", "Native Heap", "Allocation Tracker", "Sysinfo"
131     };
132
133     private static final String[] mPanelTips = new String[] {
134             "Client information", "Thread status", "VM heap status",
135             "Native heap status", "Allocation Tracker", "Sysinfo graphs"
136     };
137
138     private static final String PREFERENCE_LOGSASH =
139         "logSashLocation"; //$NON-NLS-1$
140     private static final String PREFERENCE_SASH =
141         "sashLocation"; //$NON-NLS-1$
142
143     private static final String PREFS_COL_TIME =
144         "logcat.time"; //$NON-NLS-1$
145     private static final String PREFS_COL_LEVEL =
146         "logcat.level"; //$NON-NLS-1$
147     private static final String PREFS_COL_PID =
148         "logcat.pid"; //$NON-NLS-1$
149     private static final String PREFS_COL_TAG =
150         "logcat.tag"; //$NON-NLS-1$
151     private static final String PREFS_COL_MESSAGE =
152         "logcat.message"; //$NON-NLS-1$
153
154     private static final String PREFS_FILTERS = "logcat.filter"; //$NON-NLS-1$
155
156     // singleton instance
157     private static UIThread mInstance = new UIThread();
158
159     // our display
160     private Display mDisplay;
161
162     // the table we show in the left-hand pane
163     private DevicePanel mDevicePanel;
164
165     private IDevice mCurrentDevice = null;
166     private Client mCurrentClient = null;
167
168     // status line at the bottom of the app window
169     private Label mStatusLine;
170
171     // some toolbar items we need to update
172     private ToolItem mTBShowThreadUpdates;
173     private ToolItem mTBShowHeapUpdates;
174     private ToolItem mTBHalt;
175     private ToolItem mTBCauseGc;
176     private ToolItem mTBDumpHprof;
177     private ToolItem mTBProfiling;
178
179     private ImageLoader mDdmsImageLoader;
180     private ImageLoader mDdmuiLibImageLoader;
181
182     private final class FilterStorage implements ILogFilterStorageManager {
183
184         public LogFilter[] getFilterFromStore() {
185             String filterPrefs = PrefsDialog.getStore().getString(
186                     PREFS_FILTERS);
187
188             // split in a string per filter
189             String[] filters = filterPrefs.split("\\|"); //$NON-NLS-1$
190
191             ArrayList<LogFilter> list =
192                 new ArrayList<LogFilter>(filters.length);
193
194             for (String f : filters) {
195                 if (f.length() > 0) {
196                     LogFilter logFilter = new LogFilter();
197                     if (logFilter.loadFromString(f)) {
198                         list.add(logFilter);
199                     }
200                 }
201             }
202
203             return list.toArray(new LogFilter[list.size()]);
204         }
205
206         public void saveFilters(LogFilter[] filters) {
207             StringBuilder sb = new StringBuilder();
208             for (LogFilter f : filters) {
209                 String filterString = f.toString();
210                 sb.append(filterString);
211                 sb.append('|');
212             }
213
214             PrefsDialog.getStore().setValue(PREFS_FILTERS, sb.toString());
215         }
216
217         public boolean requiresDefaultFilter() {
218             return true;
219         }
220     }
221
222
223     private LogPanel mLogPanel;
224
225     private ToolItemAction mCreateFilterAction;
226     private ToolItemAction mDeleteFilterAction;
227     private ToolItemAction mEditFilterAction;
228     private ToolItemAction mExportAction;
229     private ToolItemAction mClearAction;
230
231     private ToolItemAction[] mLogLevelActions;
232     private String[] mLogLevelIcons = {
233             "v.png", //$NON-NLS-1S
234             "d.png", //$NON-NLS-1S
235             "i.png", //$NON-NLS-1S
236             "w.png", //$NON-NLS-1S
237             "e.png", //$NON-NLS-1S
238     };
239
240     protected Clipboard mClipboard;
241
242     private MenuItem mCopyMenuItem;
243
244     private MenuItem mSelectAllMenuItem;
245
246     private TableFocusListener mTableListener;
247
248     private DeviceExplorer mExplorer = null;
249     private Shell mExplorerShell = null;
250
251     private EmulatorControlPanel mEmulatorPanel;
252
253     private EventLogPanel mEventLogPanel;
254
255     private Image mTracingStartImage;
256
257     private Image mTracingStopImage;
258
259     private class TableFocusListener implements ITableFocusListener {
260
261         private IFocusedTableActivator mCurrentActivator;
262
263         public void focusGained(IFocusedTableActivator activator) {
264             mCurrentActivator = activator;
265             if (mCopyMenuItem.isDisposed() == false) {
266                 mCopyMenuItem.setEnabled(true);
267                 mSelectAllMenuItem.setEnabled(true);
268             }
269         }
270
271         public void focusLost(IFocusedTableActivator activator) {
272             // if we move from one table to another, it's unclear
273             // if the old table lose its focus before the new
274             // one gets the focus, so we need to check.
275             if (activator == mCurrentActivator) {
276                 activator = null;
277                 if (mCopyMenuItem.isDisposed() == false) {
278                     mCopyMenuItem.setEnabled(false);
279                     mSelectAllMenuItem.setEnabled(false);
280                 }
281             }
282         }
283
284         public void copy(Clipboard clipboard) {
285             if (mCurrentActivator != null) {
286                 mCurrentActivator.copy(clipboard);
287             }
288         }
289
290         public void selectAll() {
291             if (mCurrentActivator != null) {
292                 mCurrentActivator.selectAll();
293             }
294         }
295
296     }
297
298     /**
299      * Handler for HPROF dumps.
300      * This will always prompt the user to save the HPROF file.
301      */
302     private class HProfHandler extends BaseFileHandler implements IHprofDumpHandler {
303
304         public HProfHandler(Shell parentShell) {
305             super(parentShell);
306         }
307
308         public void onFailure(final Client client) {
309             mDisplay.asyncExec(new Runnable() {
310                 public void run() {
311                     try {
312                         displayError("Unable to create HPROF file for application '%1$s'.\n" +
313                                 "Check logcat for more information.",
314                                 client.getClientData().getClientDescription());
315                     } finally {
316                         // this will make sure the dump hprof button is re-enabled for the
317                         // current selection. as the client is finished dumping an hprof file
318                         enableButtons();
319                     }
320                 }
321             });
322         }
323
324         public void onSuccess(final String remoteFilePath, final Client client) {
325             mDisplay.asyncExec(new Runnable() {
326                 public void run() {
327                     final IDevice device = client.getDevice();
328                     try {
329                         // get the sync service to pull the HPROF file
330                         final SyncService sync = client.getDevice().getSyncService();
331                         if (sync != null) {
332                             SyncResult result = promptAndPull(sync,
333                                     client.getClientData().getClientDescription() + ".hprof",
334                                     remoteFilePath, "Save HPROF file");
335                             if (result != null && result.getCode() != SyncService.RESULT_OK) {
336                                 displayError(
337                                         "Unable to download HPROF file from device '%1$s'.\n\n%2$s",
338                                         device.getSerialNumber(), result.getMessage());
339                             }
340                         } else {
341                             displayError("Unable to download HPROF file from device '%1$s'.",
342                                     device.getSerialNumber());
343                         }
344                     } catch (Exception e) {
345                         displayError("Unable to download HPROF file from device '%1$s'.",
346                                 device.getSerialNumber());
347
348                     } finally {
349                         // this will make sure the dump hprof button is re-enabled for the
350                         // current selection. as the client is finished dumping an hprof file
351                         enableButtons();
352                     }
353                 }
354             });
355         }
356
357         private void displayError(String format, Object... args) {
358             MessageDialog.openError(mParentShell, "HPROF Error",
359                     String.format(format, args));
360         }
361     }
362
363
364     /**
365      * Generic constructor.
366      */
367     private UIThread() {
368         mPanels[PANEL_INFO] = new InfoPanel();
369         mPanels[PANEL_THREAD] = new ThreadPanel();
370         mPanels[PANEL_HEAP] = new HeapPanel();
371         if (PrefsDialog.getStore().getBoolean(PrefsDialog.SHOW_NATIVE_HEAP)) {
372             mPanels[PANEL_NATIVE_HEAP] = new NativeHeapPanel();
373         } else {
374             mPanels[PANEL_NATIVE_HEAP] = null;
375         }
376         mPanels[PANEL_ALLOCATIONS] = new AllocationPanel();
377         mPanels[PANEL_SYSINFO] = new SysinfoPanel();
378     }
379
380     /**
381      * Get singleton instance of the UI thread.
382      */
383     public static UIThread getInstance() {
384         return mInstance;
385     }
386
387     /**
388      * Return the Display. Don't try this unless you're in the UI thread.
389      */
390     public Display getDisplay() {
391         return mDisplay;
392     }
393
394     public void asyncExec(Runnable r) {
395         if (mDisplay != null && mDisplay.isDisposed() == false) {
396             mDisplay.asyncExec(r);
397         }
398     }
399
400     /** returns the IPreferenceStore */
401     public IPreferenceStore getStore() {
402         return PrefsDialog.getStore();
403     }
404
405     /**
406      * Create SWT objects and drive the user interface event loop.
407      * @param location location of the folder that contains ddms.
408      */
409     public void runUI(String ddmsParentLocation) {
410         Display.setAppName("ddms");
411         mDisplay = new Display();
412         final Shell shell = new Shell(mDisplay);
413
414         // create the image loaders for DDMS and DDMUILIB
415         mDdmsImageLoader = new ImageLoader(this.getClass());
416         mDdmuiLibImageLoader = new ImageLoader(DevicePanel.class);
417
418         shell.setImage(ImageHelper.loadImage(mDdmsImageLoader, mDisplay,
419                 "ddms-icon.png", //$NON-NLS-1$
420                 100, 50, null));
421
422         Log.setLogOutput(new ILogOutput() {
423             public void printAndPromptLog(final LogLevel logLevel, final String tag,
424                     final String message) {
425                 Log.printLog(logLevel, tag, message);
426                 // dialog box only run in UI thread..
427                 mDisplay.asyncExec(new Runnable() {
428                     public void run() {
429                         Shell shell = mDisplay.getActiveShell();
430                         if (logLevel == LogLevel.ERROR) {
431                             MessageDialog.openError(shell, tag, message);
432                         } else {
433                             MessageDialog.openWarning(shell, tag, message);
434                         }
435                     }
436                 });
437             }
438
439             public void printLog(LogLevel logLevel, String tag, String message) {
440                 Log.printLog(logLevel, tag, message);
441             }
442         });
443
444         // set the handler for hprof dump
445         ClientData.setHprofDumpHandler(new HProfHandler(shell));
446         ClientData.setMethodProfilingHandler(new MethodProfilingHandler(shell));
447
448         // [try to] ensure ADB is running
449         String adbLocation;
450         if (ddmsParentLocation != null && ddmsParentLocation.length() != 0) {
451             adbLocation = ddmsParentLocation + File.separator + "adb"; //$NON-NLS-1$
452         } else {
453             adbLocation = "adb"; //$NON-NLS-1$
454         }
455
456         AndroidDebugBridge.init(true /* debugger support */);
457         AndroidDebugBridge.createBridge(adbLocation, true /* forceNewBridge */);
458
459         // we need to listen to client change to be notified of client status (profiling) change
460         AndroidDebugBridge.addClientChangeListener(this);
461
462         shell.setText("Dalvik Debug Monitor");
463         setConfirmClose(shell);
464         createMenus(shell);
465         createWidgets(shell);
466
467         shell.pack();
468         setSizeAndPosition(shell);
469         shell.open();
470
471         Log.d("ddms", "UI is up");
472
473         while (!shell.isDisposed()) {
474             if (!mDisplay.readAndDispatch())
475                 mDisplay.sleep();
476         }
477         mLogPanel.stopLogCat(true);
478
479         mDevicePanel.dispose();
480         for (TablePanel panel : mPanels) {
481             if (panel != null) {
482                 panel.dispose();
483             }
484         }
485
486         mDisplay.dispose();
487         Log.d("ddms", "UI is down");
488     }
489
490     /**
491      * Set the size and position of the main window from the preference, and
492      * setup listeners for control events (resize/move of the window)
493      */
494     private void setSizeAndPosition(final Shell shell) {
495         shell.setMinimumSize(400, 200);
496
497         // get the x/y and w/h from the prefs
498         PreferenceStore prefs = PrefsDialog.getStore();
499         int x = prefs.getInt(PrefsDialog.SHELL_X);
500         int y = prefs.getInt(PrefsDialog.SHELL_Y);
501         int w = prefs.getInt(PrefsDialog.SHELL_WIDTH);
502         int h = prefs.getInt(PrefsDialog.SHELL_HEIGHT);
503
504         // check that we're not out of the display area
505         Rectangle rect = mDisplay.getClientArea();
506         // first check the width/height
507         if (w > rect.width) {
508             w = rect.width;
509             prefs.setValue(PrefsDialog.SHELL_WIDTH, rect.width);
510         }
511         if (h > rect.height) {
512             h = rect.height;
513             prefs.setValue(PrefsDialog.SHELL_HEIGHT, rect.height);
514         }
515         // then check x. Make sure the left corner is in the screen
516         if (x < rect.x) {
517             x = rect.x;
518             prefs.setValue(PrefsDialog.SHELL_X, rect.x);
519         } else if (x >= rect.x + rect.width) {
520             x = rect.x + rect.width - w;
521             prefs.setValue(PrefsDialog.SHELL_X, rect.x);
522         }
523         // then check y. Make sure the left corner is in the screen
524         if (y < rect.y) {
525             y = rect.y;
526             prefs.setValue(PrefsDialog.SHELL_Y, rect.y);
527         } else if (y >= rect.y + rect.height) {
528             y = rect.y + rect.height - h;
529             prefs.setValue(PrefsDialog.SHELL_Y, rect.y);
530         }
531
532         // now we can set the location/size
533         shell.setBounds(x, y, w, h);
534
535         // add listener for resize/move
536         shell.addControlListener(new ControlListener() {
537             public void controlMoved(ControlEvent e) {
538                 // get the new x/y
539                 Rectangle rect = shell.getBounds();
540                 // store in pref file
541                 PreferenceStore prefs = PrefsDialog.getStore();
542                 prefs.setValue(PrefsDialog.SHELL_X, rect.x);
543                 prefs.setValue(PrefsDialog.SHELL_Y, rect.y);
544             }
545
546             public void controlResized(ControlEvent e) {
547                 // get the new w/h
548                 Rectangle rect = shell.getBounds();
549                 // store in pref file
550                 PreferenceStore prefs = PrefsDialog.getStore();
551                 prefs.setValue(PrefsDialog.SHELL_WIDTH, rect.width);
552                 prefs.setValue(PrefsDialog.SHELL_HEIGHT, rect.height);
553             }
554         });
555     }
556
557     /**
558      * Set the size and position of the file explorer window from the
559      * preference, and setup listeners for control events (resize/move of
560      * the window)
561      */
562     private void setExplorerSizeAndPosition(final Shell shell) {
563         shell.setMinimumSize(400, 200);
564
565         // get the x/y and w/h from the prefs
566         PreferenceStore prefs = PrefsDialog.getStore();
567         int x = prefs.getInt(PrefsDialog.EXPLORER_SHELL_X);
568         int y = prefs.getInt(PrefsDialog.EXPLORER_SHELL_Y);
569         int w = prefs.getInt(PrefsDialog.EXPLORER_SHELL_WIDTH);
570         int h = prefs.getInt(PrefsDialog.EXPLORER_SHELL_HEIGHT);
571
572         // check that we're not out of the display area
573         Rectangle rect = mDisplay.getClientArea();
574         // first check the width/height
575         if (w > rect.width) {
576             w = rect.width;
577             prefs.setValue(PrefsDialog.EXPLORER_SHELL_WIDTH, rect.width);
578         }
579         if (h > rect.height) {
580             h = rect.height;
581             prefs.setValue(PrefsDialog.EXPLORER_SHELL_HEIGHT, rect.height);
582         }
583         // then check x. Make sure the left corner is in the screen
584         if (x < rect.x) {
585             x = rect.x;
586             prefs.setValue(PrefsDialog.EXPLORER_SHELL_X, rect.x);
587         } else if (x >= rect.x + rect.width) {
588             x = rect.x + rect.width - w;
589             prefs.setValue(PrefsDialog.EXPLORER_SHELL_X, rect.x);
590         }
591         // then check y. Make sure the left corner is in the screen
592         if (y < rect.y) {
593             y = rect.y;
594             prefs.setValue(PrefsDialog.EXPLORER_SHELL_Y, rect.y);
595         } else if (y >= rect.y + rect.height) {
596             y = rect.y + rect.height - h;
597             prefs.setValue(PrefsDialog.EXPLORER_SHELL_Y, rect.y);
598         }
599
600         // now we can set the location/size
601         shell.setBounds(x, y, w, h);
602
603         // add listener for resize/move
604         shell.addControlListener(new ControlListener() {
605             public void controlMoved(ControlEvent e) {
606                 // get the new x/y
607                 Rectangle rect = shell.getBounds();
608                 // store in pref file
609                 PreferenceStore prefs = PrefsDialog.getStore();
610                 prefs.setValue(PrefsDialog.EXPLORER_SHELL_X, rect.x);
611                 prefs.setValue(PrefsDialog.EXPLORER_SHELL_Y, rect.y);
612             }
613
614             public void controlResized(ControlEvent e) {
615                 // get the new w/h
616                 Rectangle rect = shell.getBounds();
617                 // store in pref file
618                 PreferenceStore prefs = PrefsDialog.getStore();
619                 prefs.setValue(PrefsDialog.EXPLORER_SHELL_WIDTH, rect.width);
620                 prefs.setValue(PrefsDialog.EXPLORER_SHELL_HEIGHT, rect.height);
621             }
622         });
623     }
624
625     /*
626      * Set the confirm-before-close dialog. TODO: enable/disable in prefs. TODO:
627      * is there any point in having this?
628      */
629     private void setConfirmClose(final Shell shell) {
630         if (true)
631             return;
632
633         shell.addListener(SWT.Close, new Listener() {
634             public void handleEvent(Event event) {
635                 int style = SWT.APPLICATION_MODAL | SWT.YES | SWT.NO;
636                 MessageBox msgBox = new MessageBox(shell, style);
637                 msgBox.setText("Confirm...");
638                 msgBox.setMessage("Close DDM?");
639                 event.doit = (msgBox.open() == SWT.YES);
640             }
641         });
642     }
643
644     /*
645      * Create the menu bar and items.
646      */
647     private void createMenus(final Shell shell) {
648         // create menu bar
649         Menu menuBar = new Menu(shell, SWT.BAR);
650
651         // create top-level items
652         MenuItem fileItem = new MenuItem(menuBar, SWT.CASCADE);
653         fileItem.setText("&File");
654         MenuItem editItem = new MenuItem(menuBar, SWT.CASCADE);
655         editItem.setText("&Edit");
656         MenuItem actionItem = new MenuItem(menuBar, SWT.CASCADE);
657         actionItem.setText("&Actions");
658         MenuItem deviceItem = new MenuItem(menuBar, SWT.CASCADE);
659         deviceItem.setText("&Device");
660         MenuItem helpItem = new MenuItem(menuBar, SWT.CASCADE);
661         helpItem.setText("&Help");
662
663         // create top-level menus
664         Menu fileMenu = new Menu(menuBar);
665         fileItem.setMenu(fileMenu);
666         Menu editMenu = new Menu(menuBar);
667         editItem.setMenu(editMenu);
668         Menu actionMenu = new Menu(menuBar);
669         actionItem.setMenu(actionMenu);
670         Menu deviceMenu = new Menu(menuBar);
671         deviceItem.setMenu(deviceMenu);
672         Menu helpMenu = new Menu(menuBar);
673         helpItem.setMenu(helpMenu);
674
675         MenuItem item;
676
677         // create File menu items
678         item = new MenuItem(fileMenu, SWT.NONE);
679         item.setText("&Preferences...");
680         item.addSelectionListener(new SelectionAdapter() {
681             @Override
682             public void widgetSelected(SelectionEvent e) {
683                 PrefsDialog.run(shell);
684             }
685         });
686
687         item = new MenuItem(fileMenu, SWT.NONE);
688         item.setText("&Static Port Configuration...");
689         item.addSelectionListener(new SelectionAdapter() {
690             @Override
691             public void widgetSelected(SelectionEvent e) {
692                 StaticPortConfigDialog dlg = new StaticPortConfigDialog(shell);
693                 dlg.open();
694             }
695         });
696
697         new MenuItem(fileMenu, SWT.SEPARATOR);
698
699         item = new MenuItem(fileMenu, SWT.NONE);
700         item.setText("E&xit\tCtrl-Q");
701         item.setAccelerator('Q' | SWT.CONTROL);
702         item.addSelectionListener(new SelectionAdapter() {
703             @Override
704             public void widgetSelected(SelectionEvent e) {
705                 shell.close();
706             }
707         });
708
709         // create edit menu items
710         mCopyMenuItem = new MenuItem(editMenu, SWT.NONE);
711         mCopyMenuItem.setText("&Copy\tCtrl-C");
712         mCopyMenuItem.setAccelerator('C' | SWT.COMMAND);
713         mCopyMenuItem.addSelectionListener(new SelectionAdapter() {
714             @Override
715             public void widgetSelected(SelectionEvent e) {
716                 mTableListener.copy(mClipboard);
717             }
718         });
719
720         new MenuItem(editMenu, SWT.SEPARATOR);
721
722         mSelectAllMenuItem = new MenuItem(editMenu, SWT.NONE);
723         mSelectAllMenuItem.setText("Select &All\tCtrl-A");
724         mSelectAllMenuItem.setAccelerator('A' | SWT.COMMAND);
725         mSelectAllMenuItem.addSelectionListener(new SelectionAdapter() {
726             @Override
727             public void widgetSelected(SelectionEvent e) {
728                 mTableListener.selectAll();
729             }
730         });
731
732         // create Action menu items
733         // TODO: this should come with a confirmation dialog
734         final MenuItem actionHaltItem = new MenuItem(actionMenu, SWT.NONE);
735         actionHaltItem.setText("&Halt VM");
736         actionHaltItem.addSelectionListener(new SelectionAdapter() {
737             @Override
738             public void widgetSelected(SelectionEvent e) {
739                 mDevicePanel.killSelectedClient();
740             }
741         });
742
743         final MenuItem actionCauseGcItem = new MenuItem(actionMenu, SWT.NONE);
744         actionCauseGcItem.setText("Cause &GC");
745         actionCauseGcItem.addSelectionListener(new SelectionAdapter() {
746             @Override
747             public void widgetSelected(SelectionEvent e) {
748                 mDevicePanel.forceGcOnSelectedClient();
749             }
750         });
751
752         // configure Action items based on current state
753         actionMenu.addMenuListener(new MenuAdapter() {
754             @Override
755             public void menuShown(MenuEvent e) {
756                 actionHaltItem.setEnabled(mTBHalt.getEnabled() && mCurrentClient != null);
757                 actionCauseGcItem.setEnabled(mTBCauseGc.getEnabled() && mCurrentClient != null);
758             }
759         });
760
761         // create Device menu items
762         final MenuItem screenShotItem = new MenuItem(deviceMenu, SWT.NONE);
763         screenShotItem.setText("&Screen capture...\tCTrl-S");
764         screenShotItem.setAccelerator('S' | SWT.CONTROL);
765         screenShotItem.addSelectionListener(new SelectionAdapter() {
766             @Override
767             public void widgetSelected(SelectionEvent e) {
768                 if (mCurrentDevice != null) {
769                     ScreenShotDialog dlg = new ScreenShotDialog(shell);
770                     dlg.open(mCurrentDevice);
771                 }
772             }
773         });
774
775         new MenuItem(deviceMenu, SWT.SEPARATOR);
776
777         final MenuItem explorerItem = new MenuItem(deviceMenu, SWT.NONE);
778         explorerItem.setText("File Explorer...");
779         explorerItem.addSelectionListener(new SelectionAdapter() {
780             @Override
781             public void widgetSelected(SelectionEvent e) {
782                 createFileExplorer();
783             }
784         });
785
786         new MenuItem(deviceMenu, SWT.SEPARATOR);
787
788         final MenuItem processItem = new MenuItem(deviceMenu, SWT.NONE);
789         processItem.setText("Show &process status...");
790         processItem.addSelectionListener(new SelectionAdapter() {
791             @Override
792             public void widgetSelected(SelectionEvent e) {
793                 DeviceCommandDialog dlg;
794                 dlg = new DeviceCommandDialog("ps -x", "ps-x.txt", shell);
795                 dlg.open(mCurrentDevice);
796             }
797         });
798
799         final MenuItem deviceStateItem = new MenuItem(deviceMenu, SWT.NONE);
800         deviceStateItem.setText("Dump &device state...");
801         deviceStateItem.addSelectionListener(new SelectionAdapter() {
802             @Override
803             public void widgetSelected(SelectionEvent e) {
804                 DeviceCommandDialog dlg;
805                 dlg = new DeviceCommandDialog("/system/bin/dumpstate /proc/self/fd/0",
806                         "device-state.txt", shell);
807                 dlg.open(mCurrentDevice);
808             }
809         });
810
811         final MenuItem appStateItem = new MenuItem(deviceMenu, SWT.NONE);
812         appStateItem.setText("Dump &app state...");
813         appStateItem.setEnabled(false);
814         appStateItem.addSelectionListener(new SelectionAdapter() {
815             @Override
816             public void widgetSelected(SelectionEvent e) {
817                 DeviceCommandDialog dlg;
818                 dlg = new DeviceCommandDialog("dumpsys", "app-state.txt", shell);
819                 dlg.open(mCurrentDevice);
820             }
821         });
822
823         final MenuItem radioStateItem = new MenuItem(deviceMenu, SWT.NONE);
824         radioStateItem.setText("Dump &radio state...");
825         radioStateItem.addSelectionListener(new SelectionAdapter() {
826             @Override
827             public void widgetSelected(SelectionEvent e) {
828                 DeviceCommandDialog dlg;
829                 dlg = new DeviceCommandDialog(
830                         "cat /data/logs/radio.4 /data/logs/radio.3"
831                         + " /data/logs/radio.2 /data/logs/radio.1"
832                         + " /data/logs/radio",
833                         "radio-state.txt", shell);
834                 dlg.open(mCurrentDevice);
835             }
836         });
837
838         final MenuItem logCatItem = new MenuItem(deviceMenu, SWT.NONE);
839         logCatItem.setText("Run &logcat...");
840         logCatItem.addSelectionListener(new SelectionAdapter() {
841             @Override
842             public void widgetSelected(SelectionEvent e) {
843                 DeviceCommandDialog dlg;
844                 dlg = new DeviceCommandDialog("logcat '*:d jdwp:w'", "log.txt",
845                         shell);
846                 dlg.open(mCurrentDevice);
847             }
848         });
849
850         // configure Action items based on current state
851         deviceMenu.addMenuListener(new MenuAdapter() {
852             @Override
853             public void menuShown(MenuEvent e) {
854                 boolean deviceEnabled = mCurrentDevice != null;
855                 screenShotItem.setEnabled(deviceEnabled);
856                 explorerItem.setEnabled(deviceEnabled);
857                 processItem.setEnabled(deviceEnabled);
858                 deviceStateItem.setEnabled(deviceEnabled);
859                 appStateItem.setEnabled(deviceEnabled);
860                 radioStateItem.setEnabled(deviceEnabled);
861                 logCatItem.setEnabled(deviceEnabled);
862             }
863         });
864
865         // create Help menu items
866         item = new MenuItem(helpMenu, SWT.NONE);
867         item.setText("&Contents...");
868         item.addSelectionListener(new SelectionAdapter() {
869             @Override
870             public void widgetSelected(SelectionEvent e) {
871                 int style = SWT.APPLICATION_MODAL | SWT.OK;
872                 MessageBox msgBox = new MessageBox(shell, style);
873                 msgBox.setText("Help!");
874                 msgBox.setMessage("Help wanted.");
875                 msgBox.open();
876             }
877         });
878
879         new MenuItem(helpMenu, SWT.SEPARATOR);
880
881         item = new MenuItem(helpMenu, SWT.NONE);
882         item.setText("&About...");
883         item.addSelectionListener(new SelectionAdapter() {
884             @Override
885             public void widgetSelected(SelectionEvent e) {
886                 AboutDialog dlg = new AboutDialog(shell);
887                 dlg.open();
888             }
889         });
890
891         // tell the shell to use this menu
892         shell.setMenuBar(menuBar);
893     }
894
895     /*
896      * Create the widgets in the main application window. The basic layout is a
897      * two-panel sash, with a scrolling list of VMs on the left and detailed
898      * output for a single VM on the right.
899      */
900     private void createWidgets(final Shell shell) {
901         Color darkGray = shell.getDisplay().getSystemColor(SWT.COLOR_DARK_GRAY);
902
903         /*
904          * Create three areas: tool bar, split panels, status line
905          */
906         shell.setLayout(new GridLayout(1, false));
907
908         // 1. panel area
909         final Composite panelArea = new Composite(shell, SWT.BORDER);
910
911         // make the panel area absorb all space
912         panelArea.setLayoutData(new GridData(GridData.FILL_BOTH));
913
914         // 2. status line.
915         mStatusLine = new Label(shell, SWT.NONE);
916
917         // make status line extend all the way across
918         mStatusLine.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
919
920         mStatusLine.setText("Initializing...");
921
922         /*
923          * Configure the split-panel area.
924          */
925         final PreferenceStore prefs = PrefsDialog.getStore();
926
927         Composite topPanel = new Composite(panelArea, SWT.NONE);
928         final Sash sash = new Sash(panelArea, SWT.HORIZONTAL);
929         sash.setBackground(darkGray);
930         Composite bottomPanel = new Composite(panelArea, SWT.NONE);
931
932         panelArea.setLayout(new FormLayout());
933
934         createTopPanel(topPanel, darkGray);
935         createBottomPanel(bottomPanel);
936
937         // form layout data
938         FormData data = new FormData();
939         data.top = new FormAttachment(0, 0);
940         data.bottom = new FormAttachment(sash, 0);
941         data.left = new FormAttachment(0, 0);
942         data.right = new FormAttachment(100, 0);
943         topPanel.setLayoutData(data);
944
945         final FormData sashData = new FormData();
946         if (prefs != null && prefs.contains(PREFERENCE_LOGSASH)) {
947             sashData.top = new FormAttachment(0, prefs.getInt(
948                     PREFERENCE_LOGSASH));
949         } else {
950             sashData.top = new FormAttachment(50,0); // 50% across
951         }
952         sashData.left = new FormAttachment(0, 0);
953         sashData.right = new FormAttachment(100, 0);
954         sash.setLayoutData(sashData);
955
956         data = new FormData();
957         data.top = new FormAttachment(sash, 0);
958         data.bottom = new FormAttachment(100, 0);
959         data.left = new FormAttachment(0, 0);
960         data.right = new FormAttachment(100, 0);
961         bottomPanel.setLayoutData(data);
962
963         // allow resizes, but cap at minPanelWidth
964         sash.addListener(SWT.Selection, new Listener() {
965             public void handleEvent(Event e) {
966                 Rectangle sashRect = sash.getBounds();
967                 Rectangle panelRect = panelArea.getClientArea();
968                 int bottom = panelRect.height - sashRect.height - 100;
969                 e.y = Math.max(Math.min(e.y, bottom), 100);
970                 if (e.y != sashRect.y) {
971                     sashData.top = new FormAttachment(0, e.y);
972                     prefs.setValue(PREFERENCE_LOGSASH, e.y);
973                     panelArea.layout();
974                 }
975             }
976         });
977
978         // add a global focus listener for all the tables
979         mTableListener = new TableFocusListener();
980
981         // now set up the listener in the various panels
982         mLogPanel.setTableFocusListener(mTableListener);
983         mEventLogPanel.setTableFocusListener(mTableListener);
984         for (TablePanel p : mPanels) {
985             if (p != null) {
986                 p.setTableFocusListener(mTableListener);
987             }
988         }
989
990         mStatusLine.setText("");
991     }
992
993     /*
994      * Populate the tool bar.
995      */
996     private void createDevicePanelToolBar(ToolBar toolBar) {
997         Display display = toolBar.getDisplay();
998
999         // add "show heap updates" button
1000         mTBShowHeapUpdates = new ToolItem(toolBar, SWT.CHECK);
1001         mTBShowHeapUpdates.setImage(ImageHelper.loadImage(mDdmuiLibImageLoader, display,
1002                 DevicePanel.ICON_HEAP, DevicePanel.ICON_WIDTH, DevicePanel.ICON_WIDTH, null));
1003         mTBShowHeapUpdates.setToolTipText("Show heap updates");
1004         mTBShowHeapUpdates.setEnabled(false);
1005         mTBShowHeapUpdates.addSelectionListener(new SelectionAdapter() {
1006             @Override
1007             public void widgetSelected(SelectionEvent e) {
1008                 if (mCurrentClient != null) {
1009                     // boolean status = ((ToolItem)e.item).getSelection();
1010                     // invert previous state
1011                     boolean enable = !mCurrentClient.isHeapUpdateEnabled();
1012                     mCurrentClient.setHeapUpdateEnabled(enable);
1013                 } else {
1014                     e.doit = false; // this has no effect?
1015                 }
1016             }
1017         });
1018
1019         // add "dump HPROF" button
1020         mTBDumpHprof = new ToolItem(toolBar, SWT.PUSH);
1021         mTBDumpHprof.setToolTipText("Dump HPROF file");
1022         mTBDumpHprof.setEnabled(false);
1023         mTBDumpHprof.setImage(ImageHelper.loadImage(mDdmuiLibImageLoader, display,
1024                 DevicePanel.ICON_HPROF, DevicePanel.ICON_WIDTH, DevicePanel.ICON_WIDTH, null));
1025         mTBDumpHprof.addSelectionListener(new SelectionAdapter() {
1026             @Override
1027             public void widgetSelected(SelectionEvent e) {
1028                 mDevicePanel.dumpHprof();
1029
1030                 // this will make sure the dump hprof button is disabled for the current selection
1031                 // as the client is already dumping an hprof file
1032                 enableButtons();
1033             }
1034         });
1035
1036         // add "cause GC" button
1037         mTBCauseGc = new ToolItem(toolBar, SWT.PUSH);
1038         mTBCauseGc.setToolTipText("Cause an immediate GC");
1039         mTBCauseGc.setEnabled(false);
1040         mTBCauseGc.setImage(ImageHelper.loadImage(mDdmuiLibImageLoader, display,
1041                 DevicePanel.ICON_GC, DevicePanel.ICON_WIDTH, DevicePanel.ICON_WIDTH, null));
1042         mTBCauseGc.addSelectionListener(new SelectionAdapter() {
1043             @Override
1044             public void widgetSelected(SelectionEvent e) {
1045                 mDevicePanel.forceGcOnSelectedClient();
1046             }
1047         });
1048
1049         new ToolItem(toolBar, SWT.SEPARATOR);
1050
1051         // add "show thread updates" button
1052         mTBShowThreadUpdates = new ToolItem(toolBar, SWT.CHECK);
1053         mTBShowThreadUpdates.setImage(ImageHelper.loadImage(mDdmuiLibImageLoader, display,
1054                 DevicePanel.ICON_THREAD, DevicePanel.ICON_WIDTH, DevicePanel.ICON_WIDTH, null));
1055         mTBShowThreadUpdates.setToolTipText("Show thread updates");
1056         mTBShowThreadUpdates.setEnabled(false);
1057         mTBShowThreadUpdates.addSelectionListener(new SelectionAdapter() {
1058             @Override
1059             public void widgetSelected(SelectionEvent e) {
1060                 if (mCurrentClient != null) {
1061                     // boolean status = ((ToolItem)e.item).getSelection();
1062                     // invert previous state
1063                     boolean enable = !mCurrentClient.isThreadUpdateEnabled();
1064
1065                     mCurrentClient.setThreadUpdateEnabled(enable);
1066                 } else {
1067                     e.doit = false; // this has no effect?
1068                 }
1069             }
1070         });
1071
1072         // add a start/stop method tracing
1073         mTracingStartImage = ImageHelper.loadImage(mDdmuiLibImageLoader, display,
1074                 DevicePanel.ICON_TRACING_START,
1075                 DevicePanel.ICON_WIDTH, DevicePanel.ICON_WIDTH, null);
1076         mTracingStopImage = ImageHelper.loadImage(mDdmuiLibImageLoader, display,
1077                 DevicePanel.ICON_TRACING_STOP,
1078                 DevicePanel.ICON_WIDTH, DevicePanel.ICON_WIDTH, null);
1079         mTBProfiling = new ToolItem(toolBar, SWT.PUSH);
1080         mTBProfiling.setToolTipText("Start Method Profiling");
1081         mTBProfiling.setEnabled(false);
1082         mTBProfiling.setImage(mTracingStartImage);
1083         mTBProfiling.addSelectionListener(new SelectionAdapter() {
1084             @Override
1085             public void widgetSelected(SelectionEvent e) {
1086                 mDevicePanel.toggleMethodProfiling();
1087             }
1088         });
1089
1090         new ToolItem(toolBar, SWT.SEPARATOR);
1091
1092         // add "kill VM" button; need to make this visually distinct from
1093         // the status update buttons
1094         mTBHalt = new ToolItem(toolBar, SWT.PUSH);
1095         mTBHalt.setToolTipText("Halt the target VM");
1096         mTBHalt.setEnabled(false);
1097         mTBHalt.setImage(ImageHelper.loadImage(mDdmuiLibImageLoader, display,
1098                 DevicePanel.ICON_HALT, DevicePanel.ICON_WIDTH, DevicePanel.ICON_WIDTH, null));
1099         mTBHalt.addSelectionListener(new SelectionAdapter() {
1100             @Override
1101             public void widgetSelected(SelectionEvent e) {
1102                 mDevicePanel.killSelectedClient();
1103             }
1104         });
1105
1106        toolBar.pack();
1107     }
1108
1109     private void createTopPanel(final Composite comp, Color darkGray) {
1110         final PreferenceStore prefs = PrefsDialog.getStore();
1111
1112         comp.setLayout(new FormLayout());
1113
1114         Composite leftPanel = new Composite(comp, SWT.NONE);
1115         final Sash sash = new Sash(comp, SWT.VERTICAL);
1116         sash.setBackground(darkGray);
1117         Composite rightPanel = new Composite(comp, SWT.NONE);
1118
1119         createLeftPanel(leftPanel);
1120         createRightPanel(rightPanel);
1121
1122         FormData data = new FormData();
1123         data.top = new FormAttachment(0, 0);
1124         data.bottom = new FormAttachment(100, 0);
1125         data.left = new FormAttachment(0, 0);
1126         data.right = new FormAttachment(sash, 0);
1127         leftPanel.setLayoutData(data);
1128
1129         final FormData sashData = new FormData();
1130         sashData.top = new FormAttachment(0, 0);
1131         sashData.bottom = new FormAttachment(100, 0);
1132         if (prefs != null && prefs.contains(PREFERENCE_SASH)) {
1133             sashData.left = new FormAttachment(0, prefs.getInt(
1134                     PREFERENCE_SASH));
1135         } else {
1136             // position the sash 380 from the right instead of x% (done by using
1137             // FormAttachment(x, 0)) in order to keep the sash at the same
1138             // position
1139             // from the left when the window is resized.
1140             // 380px is just enough to display the left table with no horizontal
1141             // scrollbar with the default font.
1142             sashData.left = new FormAttachment(0, 380);
1143         }
1144         sash.setLayoutData(sashData);
1145
1146         data = new FormData();
1147         data.top = new FormAttachment(0, 0);
1148         data.bottom = new FormAttachment(100, 0);
1149         data.left = new FormAttachment(sash, 0);
1150         data.right = new FormAttachment(100, 0);
1151         rightPanel.setLayoutData(data);
1152
1153         final int minPanelWidth = 60;
1154
1155         // allow resizes, but cap at minPanelWidth
1156         sash.addListener(SWT.Selection, new Listener() {
1157             public void handleEvent(Event e) {
1158                 Rectangle sashRect = sash.getBounds();
1159                 Rectangle panelRect = comp.getClientArea();
1160                 int right = panelRect.width - sashRect.width - minPanelWidth;
1161                 e.x = Math.max(Math.min(e.x, right), minPanelWidth);
1162                 if (e.x != sashRect.x) {
1163                     sashData.left = new FormAttachment(0, e.x);
1164                     prefs.setValue(PREFERENCE_SASH, e.x);
1165                     comp.layout();
1166                 }
1167             }
1168         });
1169     }
1170
1171     private void createBottomPanel(final Composite comp) {
1172         final PreferenceStore prefs = PrefsDialog.getStore();
1173
1174         // create clipboard
1175         Display display = comp.getDisplay();
1176         mClipboard = new Clipboard(display);
1177
1178         LogColors colors = new LogColors();
1179
1180         colors.infoColor = new Color(display, 0, 127, 0);
1181         colors.debugColor = new Color(display, 0, 0, 127);
1182         colors.errorColor = new Color(display, 255, 0, 0);
1183         colors.warningColor = new Color(display, 255, 127, 0);
1184         colors.verboseColor = new Color(display, 0, 0, 0);
1185
1186         // set the preferences names
1187         LogPanel.PREFS_TIME = PREFS_COL_TIME;
1188         LogPanel.PREFS_LEVEL = PREFS_COL_LEVEL;
1189         LogPanel.PREFS_PID = PREFS_COL_PID;
1190         LogPanel.PREFS_TAG = PREFS_COL_TAG;
1191         LogPanel.PREFS_MESSAGE = PREFS_COL_MESSAGE;
1192
1193         comp.setLayout(new GridLayout(1, false));
1194
1195         ToolBar toolBar = new ToolBar(comp, SWT.HORIZONTAL);
1196
1197         mCreateFilterAction = new ToolItemAction(toolBar, SWT.PUSH);
1198         mCreateFilterAction.item.setToolTipText("Create Filter");
1199         mCreateFilterAction.item.setImage(ImageHelper.loadImage(mDdmuiLibImageLoader, mDisplay,
1200                 "add.png", //$NON-NLS-1$
1201                 DevicePanel.ICON_WIDTH, DevicePanel.ICON_WIDTH, null));
1202         mCreateFilterAction.item.addSelectionListener(new SelectionAdapter() {
1203             @Override
1204             public void widgetSelected(SelectionEvent e) {
1205                 mLogPanel.addFilter();
1206             }
1207         });
1208
1209         mEditFilterAction = new ToolItemAction(toolBar, SWT.PUSH);
1210         mEditFilterAction.item.setToolTipText("Edit Filter");
1211         mEditFilterAction.item.setImage(ImageHelper.loadImage(mDdmuiLibImageLoader, mDisplay,
1212                 "edit.png", //$NON-NLS-1$
1213                 DevicePanel.ICON_WIDTH, DevicePanel.ICON_WIDTH, null));
1214         mEditFilterAction.item.addSelectionListener(new SelectionAdapter() {
1215             @Override
1216             public void widgetSelected(SelectionEvent e) {
1217                 mLogPanel.editFilter();
1218             }
1219         });
1220
1221         mDeleteFilterAction = new ToolItemAction(toolBar, SWT.PUSH);
1222         mDeleteFilterAction.item.setToolTipText("Delete Filter");
1223         mDeleteFilterAction.item.setImage(ImageHelper.loadImage(mDdmuiLibImageLoader, mDisplay,
1224                 "delete.png", //$NON-NLS-1$
1225                 DevicePanel.ICON_WIDTH, DevicePanel.ICON_WIDTH, null));
1226         mDeleteFilterAction.item.addSelectionListener(new SelectionAdapter() {
1227             @Override
1228             public void widgetSelected(SelectionEvent e) {
1229                 mLogPanel.deleteFilter();
1230             }
1231         });
1232
1233
1234         new ToolItem(toolBar, SWT.SEPARATOR);
1235
1236         LogLevel[] levels = LogLevel.values();
1237         mLogLevelActions = new ToolItemAction[mLogLevelIcons.length];
1238         for (int i = 0 ; i < mLogLevelActions.length; i++) {
1239             String name = levels[i].getStringValue();
1240             final ToolItemAction newAction = new ToolItemAction(toolBar, SWT.CHECK);
1241             mLogLevelActions[i] = newAction;
1242             //newAction.item.setText(name);
1243             newAction.item.addSelectionListener(new SelectionAdapter() {
1244                 @Override
1245                 public void widgetSelected(SelectionEvent e) {
1246                     // disable the other actions and record current index
1247                     for (int i = 0 ; i < mLogLevelActions.length; i++) {
1248                         ToolItemAction a = mLogLevelActions[i];
1249                         if (a == newAction) {
1250                             a.setChecked(true);
1251
1252                             // set the log level
1253                             mLogPanel.setCurrentFilterLogLevel(i+2);
1254                         } else {
1255                             a.setChecked(false);
1256                         }
1257                     }
1258                 }
1259             });
1260
1261             newAction.item.setToolTipText(name);
1262             newAction.item.setImage(ImageHelper.loadImage(mDdmuiLibImageLoader, mDisplay,
1263                     mLogLevelIcons[i],
1264                     DevicePanel.ICON_WIDTH, DevicePanel.ICON_WIDTH, null));
1265         }
1266
1267         new ToolItem(toolBar, SWT.SEPARATOR);
1268
1269         mClearAction = new ToolItemAction(toolBar, SWT.PUSH);
1270         mClearAction.item.setToolTipText("Clear Log");
1271
1272         mClearAction.item.setImage(ImageHelper.loadImage(mDdmuiLibImageLoader, mDisplay,
1273                 "clear.png", //$NON-NLS-1$
1274                 DevicePanel.ICON_WIDTH, DevicePanel.ICON_WIDTH, null));
1275         mClearAction.item.addSelectionListener(new SelectionAdapter() {
1276             @Override
1277             public void widgetSelected(SelectionEvent e) {
1278                 mLogPanel.clear();
1279             }
1280         });
1281
1282         new ToolItem(toolBar, SWT.SEPARATOR);
1283
1284         mExportAction = new ToolItemAction(toolBar, SWT.PUSH);
1285         mExportAction.item.setToolTipText("Export Selection As Text...");
1286         mExportAction.item.setImage(ImageHelper.loadImage(mDdmuiLibImageLoader, mDisplay,
1287                 "save.png", //$NON-NLS-1$
1288                 DevicePanel.ICON_WIDTH, DevicePanel.ICON_WIDTH, null));
1289         mExportAction.item.addSelectionListener(new SelectionAdapter() {
1290             @Override
1291             public void widgetSelected(SelectionEvent e) {
1292                 mLogPanel.save();
1293             }
1294         });
1295
1296
1297         toolBar.pack();
1298
1299         // now create the log view
1300         mLogPanel = new LogPanel(new ImageLoader(LogPanel.class), colors, new FilterStorage(),
1301                 LogPanel.FILTER_MANUAL);
1302
1303         mLogPanel.setActions(mDeleteFilterAction, mEditFilterAction, mLogLevelActions);
1304
1305         String colMode = prefs.getString(PrefsDialog.LOGCAT_COLUMN_MODE);
1306         if (PrefsDialog.LOGCAT_COLUMN_MODE_AUTO.equals(colMode)) {
1307             mLogPanel.setColumnMode(LogPanel.COLUMN_MODE_AUTO);
1308         }
1309
1310         String fontStr = PrefsDialog.getStore().getString(PrefsDialog.LOGCAT_FONT);
1311         if (fontStr != null) {
1312             try {
1313                 FontData fdat = new FontData(fontStr);
1314                 mLogPanel.setFont(new Font(display, fdat));
1315             } catch (IllegalArgumentException e) {
1316                 // Looks like fontStr isn't a valid font representation.
1317                 // We do nothing in this case, the logcat view will use the default font.
1318             } catch (SWTError e2) {
1319                 // Looks like the Font() constructor failed.
1320                 // We do nothing in this case, the logcat view will use the default font.
1321             }
1322         }
1323
1324         mLogPanel.createPanel(comp);
1325
1326         // and start the logcat
1327         mLogPanel.startLogCat(mCurrentDevice);
1328     }
1329
1330     /*
1331      * Create the contents of the left panel: a table of VMs.
1332      */
1333     private void createLeftPanel(final Composite comp) {
1334         comp.setLayout(new GridLayout(1, false));
1335         ToolBar toolBar = new ToolBar(comp, SWT.HORIZONTAL | SWT.RIGHT | SWT.WRAP);
1336         toolBar.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
1337         createDevicePanelToolBar(toolBar);
1338
1339         Composite c = new Composite(comp, SWT.NONE);
1340         c.setLayoutData(new GridData(GridData.FILL_BOTH));
1341
1342         mDevicePanel = new DevicePanel(new ImageLoader(DevicePanel.class), true /* showPorts */);
1343         mDevicePanel.createPanel(c);
1344
1345         // add ourselves to the device panel selection listener
1346         mDevicePanel.addSelectionListener(this);
1347     }
1348
1349     /*
1350      * Create the contents of the right panel: tabs with VM information.
1351      */
1352     private void createRightPanel(final Composite comp) {
1353         TabItem item;
1354         TabFolder tabFolder;
1355
1356         comp.setLayout(new FillLayout());
1357
1358         tabFolder = new TabFolder(comp, SWT.NONE);
1359
1360         for (int i = 0; i < mPanels.length; i++) {
1361             if (mPanels[i] != null) {
1362                 item = new TabItem(tabFolder, SWT.NONE);
1363                 item.setText(mPanelNames[i]);
1364                 item.setToolTipText(mPanelTips[i]);
1365                 item.setControl(mPanels[i].createPanel(tabFolder));
1366             }
1367         }
1368
1369         // add the emulator control panel to the folders.
1370         item = new TabItem(tabFolder, SWT.NONE);
1371         item.setText("Emulator Control");
1372         item.setToolTipText("Emulator Control Panel");
1373         mEmulatorPanel = new EmulatorControlPanel(mDdmuiLibImageLoader);
1374         item.setControl(mEmulatorPanel.createPanel(tabFolder));
1375
1376         // add the event log panel to the folders.
1377         item = new TabItem(tabFolder, SWT.NONE);
1378         item.setText("Event Log");
1379         item.setToolTipText("Event Log");
1380
1381         // create the composite that will hold the toolbar and the event log panel.
1382         Composite eventLogTopComposite = new Composite(tabFolder, SWT.NONE);
1383         item.setControl(eventLogTopComposite);
1384         eventLogTopComposite.setLayout(new GridLayout(1, false));
1385
1386         // create the toolbar and the actions
1387         ToolBar toolbar = new ToolBar(eventLogTopComposite, SWT.HORIZONTAL);
1388         toolbar.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
1389
1390         ToolItemAction optionsAction = new ToolItemAction(toolbar, SWT.PUSH);
1391         optionsAction.item.setToolTipText("Opens the options panel");
1392         optionsAction.item.setImage(ImageHelper.loadImage(mDdmuiLibImageLoader, comp.getDisplay(),
1393                 "edit.png", //$NON-NLS-1$
1394                 DevicePanel.ICON_WIDTH, DevicePanel.ICON_WIDTH, null));
1395
1396         ToolItemAction clearAction = new ToolItemAction(toolbar, SWT.PUSH);
1397         clearAction.item.setToolTipText("Clears the event log");
1398         clearAction.item.setImage(ImageHelper.loadImage(mDdmuiLibImageLoader, comp.getDisplay(),
1399                 "clear.png", //$NON-NLS-1$
1400                 DevicePanel.ICON_WIDTH, DevicePanel.ICON_WIDTH, null));
1401
1402         new ToolItem(toolbar, SWT.SEPARATOR);
1403
1404         ToolItemAction saveAction = new ToolItemAction(toolbar, SWT.PUSH);
1405         saveAction.item.setToolTipText("Saves the event log");
1406         saveAction.item.setImage(ImageHelper.loadImage(mDdmuiLibImageLoader, comp.getDisplay(),
1407                 "save.png", //$NON-NLS-1$
1408                 DevicePanel.ICON_WIDTH, DevicePanel.ICON_WIDTH, null));
1409
1410         ToolItemAction loadAction = new ToolItemAction(toolbar, SWT.PUSH);
1411         loadAction.item.setToolTipText("Loads an event log");
1412         loadAction.item.setImage(ImageHelper.loadImage(mDdmuiLibImageLoader, comp.getDisplay(),
1413                 "load.png", //$NON-NLS-1$
1414                 DevicePanel.ICON_WIDTH, DevicePanel.ICON_WIDTH, null));
1415
1416         ToolItemAction importBugAction = new ToolItemAction(toolbar, SWT.PUSH);
1417         importBugAction.item.setToolTipText("Imports a bug report");
1418         importBugAction.item.setImage(ImageHelper.loadImage(mDdmuiLibImageLoader, comp.getDisplay(),
1419                 "importBug.png", //$NON-NLS-1$
1420                 DevicePanel.ICON_WIDTH, DevicePanel.ICON_WIDTH, null));
1421
1422         // create the event log panel
1423         mEventLogPanel = new EventLogPanel(mDdmuiLibImageLoader);
1424
1425         // set the external actions
1426         mEventLogPanel.setActions(optionsAction, clearAction, saveAction, loadAction,
1427                 importBugAction);
1428
1429         // create the panel
1430         mEventLogPanel.createPanel(eventLogTopComposite);
1431     }
1432
1433     private void createFileExplorer() {
1434         if (mExplorer == null) {
1435             mExplorerShell = new Shell(mDisplay);
1436
1437             // create the ui
1438             mExplorerShell.setLayout(new GridLayout(1, false));
1439
1440             // toolbar + action
1441             ToolBar toolBar = new ToolBar(mExplorerShell, SWT.HORIZONTAL);
1442             toolBar.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
1443
1444             ToolItemAction pullAction = new ToolItemAction(toolBar, SWT.PUSH);
1445             pullAction.item.setToolTipText("Pull File from Device");
1446             Image image = mDdmuiLibImageLoader.loadImage("pull.png", mDisplay); //$NON-NLS-1$
1447             if (image != null) {
1448                 pullAction.item.setImage(image);
1449             } else {
1450                 // this is for debugging purpose when the icon is missing
1451                 pullAction.item.setText("Pull"); //$NON-NLS-1$
1452             }
1453
1454             ToolItemAction pushAction = new ToolItemAction(toolBar, SWT.PUSH);
1455             pushAction.item.setToolTipText("Push file onto Device");
1456             image = mDdmuiLibImageLoader.loadImage("push.png", mDisplay); //$NON-NLS-1$
1457             if (image != null) {
1458                 pushAction.item.setImage(image);
1459             } else {
1460                 // this is for debugging purpose when the icon is missing
1461                 pushAction.item.setText("Push"); //$NON-NLS-1$
1462             }
1463
1464             ToolItemAction deleteAction = new ToolItemAction(toolBar, SWT.PUSH);
1465             deleteAction.item.setToolTipText("Delete");
1466             image = mDdmuiLibImageLoader.loadImage("delete.png", mDisplay); //$NON-NLS-1$
1467             if (image != null) {
1468                 deleteAction.item.setImage(image);
1469             } else {
1470                 // this is for debugging purpose when the icon is missing
1471                 deleteAction.item.setText("Delete"); //$NON-NLS-1$
1472             }
1473
1474             // device explorer
1475             mExplorer = new DeviceExplorer();
1476
1477             mExplorer.setImages(mDdmuiLibImageLoader.loadImage("file.png", mDisplay), //$NON-NLS-1$
1478                     mDdmuiLibImageLoader.loadImage("folder.png", mDisplay), //$NON-NLS-1$
1479                     mDdmuiLibImageLoader.loadImage("android.png", mDisplay), //$NON-NLS-1$
1480                     null);
1481             mExplorer.setActions(pushAction, pullAction, deleteAction);
1482
1483             pullAction.item.addSelectionListener(new SelectionAdapter() {
1484                 @Override
1485                 public void widgetSelected(SelectionEvent e) {
1486                     mExplorer.pullSelection();
1487                 }
1488             });
1489             pullAction.setEnabled(false);
1490
1491             pushAction.item.addSelectionListener(new SelectionAdapter() {
1492                 @Override
1493                 public void widgetSelected(SelectionEvent e) {
1494                     mExplorer.pushIntoSelection();
1495                 }
1496             });
1497             pushAction.setEnabled(false);
1498
1499             deleteAction.item.addSelectionListener(new SelectionAdapter() {
1500                 @Override
1501                 public void widgetSelected(SelectionEvent e) {
1502                     mExplorer.deleteSelection();
1503                 }
1504             });
1505             deleteAction.setEnabled(false);
1506
1507             Composite parent = new Composite(mExplorerShell, SWT.NONE);
1508             parent.setLayoutData(new GridData(GridData.FILL_BOTH));
1509
1510             mExplorer.createPanel(parent);
1511             mExplorer.switchDevice(mCurrentDevice);
1512
1513             mExplorerShell.addShellListener(new ShellListener() {
1514                 public void shellActivated(ShellEvent e) {
1515                     // pass
1516                 }
1517
1518                 public void shellClosed(ShellEvent e) {
1519                     mExplorer = null;
1520                     mExplorerShell = null;
1521                 }
1522
1523                 public void shellDeactivated(ShellEvent e) {
1524                     // pass
1525                 }
1526
1527                 public void shellDeiconified(ShellEvent e) {
1528                     // pass
1529                 }
1530
1531                 public void shellIconified(ShellEvent e) {
1532                     // pass
1533                 }
1534             });
1535
1536             mExplorerShell.pack();
1537             setExplorerSizeAndPosition(mExplorerShell);
1538             mExplorerShell.open();
1539         } else {
1540             if (mExplorerShell != null) {
1541                 mExplorerShell.forceActive();
1542             }
1543         }
1544     }
1545
1546     /**
1547      * Set the status line. TODO: make this a stack, so we can safely have
1548      * multiple things trying to set it all at once. Also specify an expiration?
1549      */
1550     public void setStatusLine(final String str) {
1551         try {
1552             mDisplay.asyncExec(new Runnable() {
1553                 public void run() {
1554                     doSetStatusLine(str);
1555                 }
1556             });
1557         } catch (SWTException swte) {
1558             if (!mDisplay.isDisposed())
1559                 throw swte;
1560         }
1561     }
1562
1563     private void doSetStatusLine(String str) {
1564         if (mStatusLine.isDisposed())
1565             return;
1566
1567         if (!mStatusLine.getText().equals(str)) {
1568             mStatusLine.setText(str);
1569
1570             // try { Thread.sleep(100); }
1571             // catch (InterruptedException ie) {}
1572         }
1573     }
1574
1575     public void displayError(final String msg) {
1576         try {
1577             mDisplay.syncExec(new Runnable() {
1578                 public void run() {
1579                     MessageDialog.openError(mDisplay.getActiveShell(), "Error",
1580                             msg);
1581                 }
1582             });
1583         } catch (SWTException swte) {
1584             if (!mDisplay.isDisposed())
1585                 throw swte;
1586         }
1587     }
1588
1589     private void enableButtons() {
1590         if (mCurrentClient != null) {
1591             mTBShowThreadUpdates.setSelection(mCurrentClient.isThreadUpdateEnabled());
1592             mTBShowThreadUpdates.setEnabled(true);
1593             mTBShowHeapUpdates.setSelection(mCurrentClient.isHeapUpdateEnabled());
1594             mTBShowHeapUpdates.setEnabled(true);
1595             mTBHalt.setEnabled(true);
1596             mTBCauseGc.setEnabled(true);
1597
1598             ClientData data = mCurrentClient.getClientData();
1599
1600             if (data.hasFeature(ClientData.FEATURE_HPROF)) {
1601                 mTBDumpHprof.setEnabled(data.hasPendingHprofDump() == false);
1602                 mTBDumpHprof.setToolTipText("Dump HPROF file");
1603             } else {
1604                 mTBDumpHprof.setEnabled(false);
1605                 mTBDumpHprof.setToolTipText("Dump HPROF file (not supported by this VM)");
1606             }
1607
1608             if (data.hasFeature(ClientData.FEATURE_PROFILING)) {
1609                 mTBProfiling.setEnabled(true);
1610                 if (data.getMethodProfilingStatus() == MethodProfilingStatus.ON) {
1611                     mTBProfiling.setToolTipText("Stop Method Profiling");
1612                     mTBProfiling.setImage(mTracingStopImage);
1613                 } else {
1614                     mTBProfiling.setToolTipText("Start Method Profiling");
1615                     mTBProfiling.setImage(mTracingStartImage);
1616                 }
1617             } else {
1618                 mTBProfiling.setEnabled(false);
1619                 mTBProfiling.setImage(mTracingStartImage);
1620                 mTBProfiling.setToolTipText("Start Method Profiling (not supported by this VM)");
1621             }
1622         } else {
1623             // list is empty, disable these
1624             mTBShowThreadUpdates.setSelection(false);
1625             mTBShowThreadUpdates.setEnabled(false);
1626             mTBShowHeapUpdates.setSelection(false);
1627             mTBShowHeapUpdates.setEnabled(false);
1628             mTBHalt.setEnabled(false);
1629             mTBCauseGc.setEnabled(false);
1630
1631             mTBDumpHprof.setEnabled(false);
1632             mTBDumpHprof.setToolTipText("Dump HPROF file");
1633
1634             mTBProfiling.setEnabled(false);
1635             mTBProfiling.setImage(mTracingStartImage);
1636             mTBProfiling.setToolTipText("Start Method Profiling");
1637         }
1638     }
1639
1640     /**
1641      * Sent when a new {@link IDevice} and {@link Client} are selected.
1642      * @param selectedDevice the selected device. If null, no devices are selected.
1643      * @param selectedClient The selected client. If null, no clients are selected.
1644      *
1645      * @see IUiSelectionListener
1646      */
1647     public void selectionChanged(IDevice selectedDevice, Client selectedClient) {
1648         if (mCurrentDevice != selectedDevice) {
1649             mCurrentDevice = selectedDevice;
1650             for (TablePanel panel : mPanels) {
1651                 if (panel != null) {
1652                     panel.deviceSelected(mCurrentDevice);
1653                 }
1654             }
1655
1656             mEmulatorPanel.deviceSelected(mCurrentDevice);
1657             mLogPanel.deviceSelected(mCurrentDevice);
1658             if (mEventLogPanel != null) {
1659                 mEventLogPanel.deviceSelected(mCurrentDevice);
1660             }
1661
1662             if (mExplorer != null) {
1663                 mExplorer.switchDevice(mCurrentDevice);
1664             }
1665         }
1666
1667         if (mCurrentClient != selectedClient) {
1668             AndroidDebugBridge.getBridge().setSelectedClient(selectedClient);
1669             mCurrentClient = selectedClient;
1670             for (TablePanel panel : mPanels) {
1671                 if (panel != null) {
1672                     panel.clientSelected(mCurrentClient);
1673                 }
1674             }
1675
1676             enableButtons();
1677         }
1678     }
1679
1680     public void clientChanged(Client client, int changeMask) {
1681         if ((changeMask & Client.CHANGE_METHOD_PROFILING_STATUS) ==
1682                 Client.CHANGE_METHOD_PROFILING_STATUS) {
1683             if (mCurrentClient == client) {
1684                 mDisplay.asyncExec(new Runnable() {
1685                     public void run() {
1686                         // force refresh of the button enabled state.
1687                         enableButtons();
1688                     }
1689                 });
1690             }
1691         }
1692     }
1693 }