OSDN Git Service

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