OSDN Git Service

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