OSDN Git Service

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