• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 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.sdkuilib.internal.repository.sdkman2;
18 
19 import com.android.sdklib.ISdkLog;
20 import com.android.sdkuilib.internal.tasks.ILogUiProvider;
21 import com.android.sdkuilib.ui.GridDataBuilder;
22 import com.android.sdkuilib.ui.GridLayoutBuilder;
23 
24 import org.eclipse.swt.SWT;
25 import org.eclipse.swt.custom.StyleRange;
26 import org.eclipse.swt.custom.StyledText;
27 import org.eclipse.swt.events.SelectionAdapter;
28 import org.eclipse.swt.events.SelectionEvent;
29 import org.eclipse.swt.events.ShellAdapter;
30 import org.eclipse.swt.events.ShellEvent;
31 import org.eclipse.swt.graphics.Point;
32 import org.eclipse.swt.graphics.Rectangle;
33 import org.eclipse.swt.widgets.Button;
34 import org.eclipse.swt.widgets.Composite;
35 import org.eclipse.swt.widgets.Display;
36 import org.eclipse.swt.widgets.Label;
37 import org.eclipse.swt.widgets.Shell;
38 import org.eclipse.swt.widgets.Widget;
39 
40 
41 /**
42  * A floating log window that can be displayed or hidden by the main SDK Manager 2 window.
43  * It displays a log of the sdk manager operation (listing, install, delete) including
44  * any errors (e.g. network error or install/delete errors.)
45  * <p/>
46  * Since the SDK Manager will direct all log to this window, its purpose is to be
47  * opened by the main window at startup and left open all the time. When not needed
48  * the floating window is hidden but not closed. This way it can easily accumulate
49  * all the log.
50  */
51 class LogWindow implements ILogUiProvider {
52 
53     private Shell mParentShell;
54     private Shell mShell;
55     private Composite mRootComposite;
56     private StyledText mStyledText;
57     private Label mLogDescription;
58     private Button mCloseButton;
59 
60     private final ISdkLog mSecondaryLog;
61     private boolean mCloseRequested;
62     private boolean mInitPosition = true;
63     private String mLastLogMsg = null;
64 
65     private enum TextStyle {
66         DEFAULT,
67         TITLE,
68         ERROR
69     }
70 
71     /**
72      * Creates the floating window. Callers should use {@link #open()} later.
73      *
74      * @param parentShell Parent container
75      * @param secondaryLog An optional logger where messages will <em>also</em> be output.
76      */
LogWindow(Shell parentShell, ISdkLog secondaryLog)77     public LogWindow(Shell parentShell, ISdkLog secondaryLog) {
78         mParentShell = parentShell;
79         mSecondaryLog = secondaryLog;
80     }
81 
82     /**
83      * For testing only. See {@link #open()} and {@link #close()} for normal usage.
84      * @wbp.parser.entryPoint
85      */
openBlocking()86     void openBlocking() {
87         open();
88         Display display = Display.getDefault();
89         while (!mShell.isDisposed()) {
90             if (!display.readAndDispatch()) {
91                 display.sleep();
92             }
93         }
94         close();
95     }
96 
97     /**
98      * Opens the window.
99      * This call does not block and relies on the fact that the main window is
100      * already running an SWT event dispatch loop.
101      * Caller should use {@link #close()} later.
102      */
open()103     public void open() {
104         createShell();
105         createContents();
106         mShell.open();
107         mShell.layout();
108         mShell.setVisible(false);
109     }
110 
111     /**
112      * Closes and <em>destroys</em> the window.
113      * This must be called just before quitting the app.
114      * <p/>
115      * To simply hide/show the window, use {@link #setVisible(boolean)} instead.
116      */
close()117     public void close() {
118         if (mShell != null && !mShell.isDisposed()) {
119             mCloseRequested = true;
120             mShell.close();
121             mShell = null;
122         }
123     }
124 
125     /**
126      * Determines whether the window is currently shown or not.
127      *
128      * @return True if the window is shown.
129      */
isVisible()130     public boolean isVisible() {
131         return mShell != null && !mShell.isDisposed() && mShell.isVisible();
132     }
133 
134     /**
135      * Toggles the window visibility.
136      *
137      * @param visible True to make the window visible, false to hide it.
138      */
setVisible(boolean visible)139     public void setVisible(boolean visible) {
140         if (mShell != null && !mShell.isDisposed()) {
141             mShell.setVisible(visible);
142             if (visible && mInitPosition) {
143                 mInitPosition = false;
144                 positionWindow();
145             }
146         }
147     }
148 
createShell()149     private void createShell() {
150         mShell = new Shell(mParentShell, SWT.SHELL_TRIM | SWT.TOOL);
151         mShell.setMinimumSize(new Point(600, 300));
152         mShell.setSize(450, 300);
153         mShell.setText("Android SDK Manager Log");
154         GridLayoutBuilder.create(mShell);
155 
156         mShell.addShellListener(new ShellAdapter() {
157             @Override
158             public void shellClosed(ShellEvent e) {
159                 if (!mCloseRequested) {
160                     e.doit = false;
161                     setVisible(false);
162                 }
163             }
164         });
165     }
166 
167     /**
168      * Create contents of the dialog.
169      */
createContents()170     private void createContents() {
171         mRootComposite = new Composite(mShell, SWT.NONE);
172         GridLayoutBuilder.create(mRootComposite).columns(2);
173         GridDataBuilder.create(mRootComposite).fill().grab();
174 
175         mStyledText = new StyledText(mRootComposite,
176                 SWT.BORDER | SWT.MULTI | SWT.READ_ONLY | SWT.WRAP | SWT.V_SCROLL);
177         GridDataBuilder.create(mStyledText).hSpan(2).fill().grab();
178 
179         mLogDescription = new Label(mRootComposite, SWT.NONE);
180         GridDataBuilder.create(mLogDescription).hFill().hGrab();
181 
182         mCloseButton = new Button(mRootComposite, SWT.NONE);
183         mCloseButton.setText("Close");
184         mCloseButton.setToolTipText("Closes the log window");
185         mCloseButton.addSelectionListener(new SelectionAdapter() {
186             @Override
187             public void widgetSelected(SelectionEvent e) {
188                 setVisible(false);  //$hide$
189             }
190         });
191     }
192 
193     // --- Implementation of ILogUiProvider ---
194 
195 
196     /**
197      * Sets the description in the current task dialog.
198      * This method can be invoked from a non-UI thread.
199      */
200     @Override
setDescription(final String description)201     public void setDescription(final String description) {
202         syncExec(mLogDescription, new Runnable() {
203             @Override
204             public void run() {
205                 mLogDescription.setText(description);
206 
207                 if (acceptLog(description, true /*isDescription*/)) {
208                     appendLine(TextStyle.TITLE, description);
209 
210                     if (mSecondaryLog != null) {
211                         mSecondaryLog.printf("%1$s", description);  //$NON-NLS-1$
212                     }
213                 }
214             }
215         });
216     }
217 
218     /**
219      * Logs a "normal" information line.
220      * This method can be invoked from a non-UI thread.
221      */
222     @Override
log(final String log)223     public void log(final String log) {
224         if (acceptLog(log, false /*isDescription*/)) {
225             syncExec(mLogDescription, new Runnable() {
226                 @Override
227                 public void run() {
228                     appendLine(TextStyle.DEFAULT, log);
229                 }
230             });
231 
232             if (mSecondaryLog != null) {
233                 mSecondaryLog.printf("  %1$s", log);                //$NON-NLS-1$
234             }
235         }
236     }
237 
238     /**
239      * Logs an "error" information line.
240      * This method can be invoked from a non-UI thread.
241      */
242     @Override
logError(final String log)243     public void logError(final String log) {
244         if (acceptLog(log, false /*isDescription*/)) {
245             syncExec(mLogDescription, new Runnable() {
246                 @Override
247                 public void run() {
248                     appendLine(TextStyle.ERROR, log);
249                 }
250             });
251 
252             if (mSecondaryLog != null) {
253                 mSecondaryLog.error(null, "%1$s", log);             //$NON-NLS-1$
254             }
255         }
256     }
257 
258     /**
259      * Logs a "verbose" information line, that is extra details which are typically
260      * not that useful for the end-user and might be hidden until explicitly shown.
261      * This method can be invoked from a non-UI thread.
262      */
263     @Override
logVerbose(final String log)264     public void logVerbose(final String log) {
265         if (acceptLog(log, false /*isDescription*/)) {
266             syncExec(mLogDescription, new Runnable() {
267                 @Override
268                 public void run() {
269                     appendLine(TextStyle.DEFAULT, "  " + log);      //$NON-NLS-1$
270                 }
271             });
272 
273             if (mSecondaryLog != null) {
274                 mSecondaryLog.printf("    %1$s", log);              //$NON-NLS-1$
275             }
276         }
277     }
278 
279 
280     // ----
281 
282 
283     /**
284      * Centers the dialog in its parent shell.
285      */
positionWindow()286     private void positionWindow() {
287         // Centers the dialog in its parent shell
288         Shell child = mShell;
289         if (child != null && mParentShell != null) {
290             // get the parent client area with a location relative to the display
291             Rectangle parentArea = mParentShell.getClientArea();
292             Point parentLoc = mParentShell.getLocation();
293             int px = parentLoc.x;
294             int py = parentLoc.y;
295             int pw = parentArea.width;
296             int ph = parentArea.height;
297 
298             Point childSize = child.getSize();
299             int cw = Math.max(childSize.x, pw);
300             int ch = childSize.y;
301 
302             int x = 30 + px + (pw - cw) / 2;
303             if (x < 0) x = 0;
304 
305             int y = py + (ph - ch) / 2;
306             if (y < py) y = py;
307 
308             child.setLocation(x, y);
309             child.setSize(cw, ch);
310         }
311     }
312 
appendLine(TextStyle style, String text)313     private void appendLine(TextStyle style, String text) {
314         if (!text.endsWith("\n")) {                                 //$NON-NLS-1$
315             text += '\n';
316         }
317 
318         int start = mStyledText.getCharCount();
319 
320         if (style == TextStyle.DEFAULT) {
321             mStyledText.append(text);
322 
323         } else {
324             mStyledText.append(text);
325 
326             StyleRange sr = new StyleRange();
327             sr.start = start;
328             sr.length = text.length();
329             sr.fontStyle = SWT.BOLD;
330             if (style == TextStyle.ERROR) {
331                 sr.foreground = mStyledText.getDisplay().getSystemColor(SWT.COLOR_DARK_RED);
332             }
333             sr.underline = false;
334             mStyledText.setStyleRange(sr);
335         }
336 
337         // Scroll caret if it was already at the end before we added new text.
338         // Ideally we would scroll if the scrollbar is at the bottom but we don't
339         // have direct access to the scrollbar without overriding the SWT impl.
340         if (mStyledText.getCaretOffset() >= start) {
341             mStyledText.setSelection(mStyledText.getCharCount());
342         }
343     }
344 
345 
syncExec(final Widget widget, final Runnable runnable)346     private void syncExec(final Widget widget, final Runnable runnable) {
347         if (widget != null && !widget.isDisposed()) {
348             widget.getDisplay().syncExec(runnable);
349         }
350     }
351 
352     /**
353      * Filter messages displayed in the log: <br/>
354      * - Messages with a % are typical part of a progress update and shouldn't be in the log. <br/>
355      * - Messages that are the same as the same output message should be output a second time.
356      *
357      * @param msg The potential log line to print.
358      * @return True if the log line should be printed, false otherwise.
359      */
acceptLog(String msg, boolean isDescription)360     private boolean acceptLog(String msg, boolean isDescription) {
361         if (msg == null) {
362             return false;
363         }
364 
365         msg = msg.trim();
366 
367         // Descriptions also have the download progress status (0..100%) which we want to avoid
368         if (isDescription && msg.indexOf('%') != -1) {
369             return false;
370         }
371 
372         if (msg.equals(mLastLogMsg)) {
373             return false;
374         }
375 
376         mLastLogMsg = msg;
377         return true;
378     }
379 }
380