OSDN Git Service

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