OSDN Git Service

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