2 * Copyright (C) 2009 The Android Open Source Project
\r
4 * Licensed under the Apache License, Version 2.0 (the "License");
\r
5 * you may not use this file except in compliance with the License.
\r
6 * You may obtain a copy of the License at
\r
8 * http://www.apache.org/licenses/LICENSE-2.0
\r
10 * Unless required by applicable law or agreed to in writing, software
\r
11 * distributed under the License is distributed on an "AS IS" BASIS,
\r
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
\r
13 * See the License for the specific language governing permissions and
\r
14 * limitations under the License.
\r
17 package com.android.sdkuilib.internal.repository;
\r
19 import com.android.sdklib.SdkConstants;
\r
20 import com.android.sdklib.internal.repository.Archive;
\r
21 import com.android.sdkuilib.internal.repository.icons.ImageFactory;
\r
23 import org.eclipse.jface.viewers.ISelection;
\r
24 import org.eclipse.jface.viewers.IStructuredContentProvider;
\r
25 import org.eclipse.jface.viewers.IStructuredSelection;
\r
26 import org.eclipse.jface.viewers.LabelProvider;
\r
27 import org.eclipse.jface.viewers.TableViewer;
\r
28 import org.eclipse.jface.viewers.Viewer;
\r
29 import org.eclipse.swt.SWT;
\r
30 import org.eclipse.swt.custom.SashForm;
\r
31 import org.eclipse.swt.custom.StyleRange;
\r
32 import org.eclipse.swt.custom.StyledText;
\r
33 import org.eclipse.swt.events.ControlAdapter;
\r
34 import org.eclipse.swt.events.ControlEvent;
\r
35 import org.eclipse.swt.events.SelectionAdapter;
\r
36 import org.eclipse.swt.events.SelectionEvent;
\r
37 import org.eclipse.swt.events.ShellAdapter;
\r
38 import org.eclipse.swt.events.ShellEvent;
\r
39 import org.eclipse.swt.graphics.Image;
\r
40 import org.eclipse.swt.graphics.Point;
\r
41 import org.eclipse.swt.graphics.Rectangle;
\r
42 import org.eclipse.swt.layout.GridData;
\r
43 import org.eclipse.swt.layout.GridLayout;
\r
44 import org.eclipse.swt.widgets.Button;
\r
45 import org.eclipse.swt.widgets.Composite;
\r
46 import org.eclipse.swt.widgets.Dialog;
\r
47 import org.eclipse.swt.widgets.Display;
\r
48 import org.eclipse.swt.widgets.Group;
\r
49 import org.eclipse.swt.widgets.Label;
\r
50 import org.eclipse.swt.widgets.Shell;
\r
51 import org.eclipse.swt.widgets.Table;
\r
52 import org.eclipse.swt.widgets.TableColumn;
\r
54 import java.util.ArrayList;
\r
55 import java.util.Collection;
\r
56 import java.util.Comparator;
\r
57 import java.util.HashSet;
\r
58 import java.util.Map;
\r
59 import java.util.TreeMap;
\r
63 * Implements an {@link UpdateChooserDialog}.
\r
65 final class UpdateChooserDialog extends Dialog {
\r
68 * Min Y location for dialog. Need to deal with the menu bar on mac os.
\r
70 private final static int MIN_Y = SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_DARWIN ?
\r
73 /** Last dialog size for this session. */
\r
74 private static Point sLastSize;
\r
75 private boolean mCompleted;
\r
76 private final Map<Archive, Archive> mNewToOldArchiveMap;
\r
77 private boolean mLicenseAcceptAll;
\r
78 private boolean mInternalLicenseRadioUpdate;
\r
79 private HashSet<Archive> mAccepted = new HashSet<Archive>();
\r
80 private HashSet<Archive> mRejected = new HashSet<Archive>();
\r
81 private ArrayList<Archive> mResult = new ArrayList<Archive>();
\r
84 private Shell mDialogShell;
\r
85 private SashForm mSashForm;
\r
86 private Composite mPackageRootComposite;
\r
87 private Button mCancelButton;
\r
88 private Button mInstallButton;
\r
89 private TableViewer mTableViewPackage;
\r
90 private Table mTablePackage;
\r
91 private TableColumn mTableColum;
\r
92 private StyledText mPackageText;
\r
93 private Button mLicenseRadioAccept;
\r
94 private Button mLicenseRadioReject;
\r
95 private Button mLicenseRadioAcceptAll;
\r
96 private Group mPackageTextGroup;
\r
97 private final UpdaterData mUpdaterData;
\r
98 private Group mTableGroup;
\r
102 * Create the dialog.
\r
103 * @param parentShell The shell to use, typically updaterData.getWindowShell()
\r
104 * @param updaterData The updater data
\r
105 * @param newToOldUpdates The map [new archive => old archive] of potential updates
\r
107 public UpdateChooserDialog(Shell parentShell,
\r
108 UpdaterData updaterData,
\r
109 Map<Archive, Archive> newToOldUpdates) {
\r
111 SWT.APPLICATION_MODAL);
\r
112 mUpdaterData = updaterData;
\r
114 mNewToOldArchiveMap = new TreeMap<Archive, Archive>(new Comparator<Archive>() {
\r
115 public int compare(Archive a1, Archive a2) {
\r
116 // The items are archive but what we show are packages so we'll
\r
117 // sort of packages short descriptions
\r
118 String desc1 = a1.getParentPackage().getShortDescription();
\r
119 String desc2 = a2.getParentPackage().getShortDescription();
\r
120 return desc1.compareTo(desc2);
\r
123 mNewToOldArchiveMap.putAll(newToOldUpdates);
\r
127 * Returns the results, i.e. the list of selected new archives to install.
\r
128 * The list is always non null. It is empty when cancel is selected or when
\r
129 * all potential updates have been refused.
\r
131 public Collection<Archive> getResult() {
\r
136 * Open the dialog and blocks till it gets closed
\r
138 public void open() {
\r
140 positionShell(); //$hide$ (hide from SWT designer)
\r
141 mDialogShell.open();
\r
142 mDialogShell.layout();
\r
144 postCreate(); //$hide$ (hide from SWT designer)
\r
146 Display display = getParent().getDisplay();
\r
147 while (!mDialogShell.isDisposed() && !mCompleted) {
\r
148 if (!display.readAndDispatch()) {
\r
153 if (!mDialogShell.isDisposed()) {
\r
154 mDialogShell.close();
\r
159 * Create contents of the dialog.
\r
161 private void createContents() {
\r
162 mDialogShell = new Shell(getParent(), SWT.DIALOG_TRIM | SWT.RESIZE | SWT.MIN | SWT.MAX);
\r
163 mDialogShell.addShellListener(new ShellAdapter() {
\r
165 public void shellClosed(ShellEvent e) {
\r
169 mDialogShell.setLayout(new GridLayout(3, false/*makeColumnsEqual*/));
\r
170 mDialogShell.setSize(600, 400);
\r
171 mDialogShell.setText("Choose Packages to Install");
\r
174 mSashForm = new SashForm(mDialogShell, SWT.NONE);
\r
175 mSashForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 3, 1));
\r
178 // Left part of Sash Form
\r
180 mTableGroup = new Group(mSashForm, SWT.NONE);
\r
181 mTableGroup.setText("Packages");
\r
182 mTableGroup.setLayout(new GridLayout(1, false/*makeColumnsEqual*/));
\r
184 mTableViewPackage = new TableViewer(mTableGroup, SWT.BORDER | SWT.V_SCROLL | SWT.SINGLE);
\r
185 mTablePackage = mTableViewPackage.getTable();
\r
186 mTablePackage.setHeaderVisible(false);
\r
187 mTablePackage.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
\r
189 mTablePackage.addSelectionListener(new SelectionAdapter() {
\r
191 public void widgetSelected(SelectionEvent e) {
\r
192 onPackageSelected(); //$hide$
\r
195 public void widgetDefaultSelected(SelectionEvent e) {
\r
196 onPackageDoubleClick();
\r
200 mTableColum = new TableColumn(mTablePackage, SWT.NONE);
\r
201 mTableColum.setWidth(100);
\r
202 mTableColum.setText("Packages");
\r
205 // Right part of Sash form
\r
206 mPackageRootComposite = new Composite(mSashForm, SWT.NONE);
\r
207 mPackageRootComposite.setLayout(new GridLayout(4, false/*makeColumnsEqual*/));
\r
208 mPackageRootComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
\r
210 mPackageTextGroup = new Group(mPackageRootComposite, SWT.NONE);
\r
211 mPackageTextGroup.setText("Package Description && License");
\r
212 mPackageTextGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 4, 1));
\r
213 mPackageTextGroup.setLayout(new GridLayout(1, false/*makeColumnsEqual*/));
\r
215 mPackageText = new StyledText(mPackageTextGroup,
\r SWT.MULTI | SWT.READ_ONLY | SWT.WRAP | SWT.V_SCROLL);
\r
216 mPackageText.setBackground(
\r
217 getParent().getDisplay().getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));
\r
218 mPackageText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
\r
220 mLicenseRadioAccept = new Button(mPackageRootComposite, SWT.RADIO);
\r
221 mLicenseRadioAccept.setText("Accept");
\r
222 mLicenseRadioAccept.addSelectionListener(new SelectionAdapter() {
\r
224 public void widgetSelected(SelectionEvent e) {
\r
225 onLicenseRadioSelected();
\r
229 mLicenseRadioReject = new Button(mPackageRootComposite, SWT.RADIO);
\r
230 mLicenseRadioReject.setText("Reject");
\r
231 mLicenseRadioReject.addSelectionListener(new SelectionAdapter() {
\r
233 public void widgetSelected(SelectionEvent e) {
\r
234 onLicenseRadioSelected();
\r
238 Label placeholder = new Label(mPackageRootComposite, SWT.NONE);
\r
239 placeholder.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false, 1, 1));
\r
241 mLicenseRadioAcceptAll = new Button(mPackageRootComposite, SWT.RADIO);
\r
242 mLicenseRadioAcceptAll.setText("Accept All");
\r
243 mLicenseRadioAcceptAll.addSelectionListener(new SelectionAdapter() {
\r
245 public void widgetSelected(SelectionEvent e) {
\r
246 onLicenseRadioSelected();
\r
250 mSashForm.setWeights(new int[] {200, 300});
\r
253 placeholder = new Label(mDialogShell, SWT.NONE);
\r
254 placeholder.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, true, false, 1, 1));
\r
256 // for MacOS, the Cancel button should be left.
\r
257 if (SdkConstants.currentPlatform() == SdkConstants.PLATFORM_DARWIN) {
\r
258 mCancelButton = new Button(mDialogShell, SWT.PUSH);
\r
261 mInstallButton = new Button(mDialogShell, SWT.PUSH);
\r
262 mInstallButton.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
\r
263 mInstallButton.setText("Install Accepted");
\r
264 mInstallButton.addSelectionListener(new SelectionAdapter() {
\r
266 public void widgetSelected(SelectionEvent e) {
\r
267 onInstallSelected();
\r
271 // if we haven't created the cancel button yet (macos?), create it now.
\r
272 if (mCancelButton == null) {
\r
273 mCancelButton = new Button(mDialogShell, SWT.PUSH);
\r
275 mCancelButton.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
\r
276 mCancelButton.setText("Cancel");
\r
277 mCancelButton.addSelectionListener(new SelectionAdapter() {
\r
279 public void widgetSelected(SelectionEvent e) {
\r
280 onCancelSelected();
\r
285 // -- End of UI, Start of internal logic ----------
\r
286 // Hide everything down-below from SWT designer
\r
290 * Starts the thread that runs the task.
\r
291 * This is deferred till the UI is created.
\r
293 private void postCreate() {
\r
296 // Automatically accept those with an empty license
\r
297 for (Archive a : mNewToOldArchiveMap.keySet()) {
\r
299 String license = a.getParentPackage().getLicense();
\r
300 if (license != null) {
\r
301 license = license.trim();
\r
302 if (license.length() == 0) {
\r
310 // Fill the list with the replacement packages
\r
311 mTableViewPackage.setLabelProvider(new NewArchivesLabelProvider());
\r
312 mTableViewPackage.setContentProvider(new NewArchivesContentProvider());
\r
313 mTableViewPackage.setInput(mNewToOldArchiveMap);
\r
315 adjustColumnsWidth();
\r
317 // select first item
\r
318 mTablePackage.select(0);
\r
319 onPackageSelected();
\r
323 * Creates the icon of the window shell.
\r
325 private void setWindowImage() {
\r
326 String imageName = "android_icon_16.png"; //$NON-NLS-1$
\r
327 if (SdkConstants.currentPlatform() == SdkConstants.PLATFORM_DARWIN) {
\r
328 imageName = "android_icon_128.png"; //$NON-NLS-1$
\r
331 if (mUpdaterData != null) {
\r
332 ImageFactory imgFactory = mUpdaterData.getImageFactory();
\r
333 if (imgFactory != null) {
\r
334 mDialogShell.setImage(imgFactory.getImageByName(imageName));
\r
340 * Adds a listener to adjust the columns width when the parent is resized.
\r
342 * If we need something more fancy, we might want to use this:
\r
343 * http://dev.eclipse.org/viewcvs/index.cgi/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet77.java?view=co
\r
345 private void adjustColumnsWidth() {
\r
346 // Add a listener to resize the column to the full width of the table
\r
347 ControlAdapter resizer = new ControlAdapter() {
\r
349 public void controlResized(ControlEvent e) {
\r
350 Rectangle r = mTablePackage.getClientArea();
\r
351 mTableColum.setWidth(r.width);
\r
354 mTablePackage.addControlListener(resizer);
\r
355 resizer.controlResized(null);
\r
359 * Callback invoked when the shell is closed either by clicking the close button
\r
360 * on by calling shell.close().
\r
361 * Captures the window size before closing this.
\r
363 private void onShellClosed(ShellEvent e) {
\r
364 sLastSize = mDialogShell.getSize();
\r
368 * Centers the dialog in its parent shell.
\r
370 private void positionShell() {
\r
371 // Centers the dialog in its parent shell
\r
372 Shell child = mDialogShell;
\r
373 Shell parent = getParent();
\r
374 if (child != null && parent != null) {
\r
376 // get the parent client area with a location relative to the display
\r
377 Rectangle parentArea = parent.getClientArea();
\r
378 Point parentLoc = parent.getLocation();
\r
379 int px = parentLoc.x;
\r
380 int py = parentLoc.y;
\r
381 int pw = parentArea.width;
\r
382 int ph = parentArea.height;
\r
384 // Reuse the last size if there's one, otherwise use the default
\r
385 Point childSize = sLastSize != null ? sLastSize : child.getSize();
\r
386 int cw = childSize.x;
\r
387 int ch = childSize.y;
\r
389 int x = px + (pw - cw) / 2;
\r
390 int y = py + (ph - ch) / 2;
\r
400 child.setLocation(x, y);
\r
401 child.setSize(cw, ch);
\r
406 * Callback invoked when the Install button is selected. Fills {@link #mResult} and
\r
407 * completes the dialog.
\r
409 private void onInstallSelected() {
\r
410 // get list of accepted items
\r
411 mResult.addAll(mAccepted);
\r
416 * Callback invoked when the Cancel button is selected.
\r
418 private void onCancelSelected() {
\r
423 * Callback invoked when a package item is selected in the list.
\r
425 private void onPackageSelected() {
\r
426 Archive a = getSelectedArchive();
\r
427 displayInformation(a);
\r
428 updateLicenceRadios(a);
\r
431 /** Returns the currently selected Archive or null. */
\r
432 private Archive getSelectedArchive() {
\r
433 ISelection sel = mTableViewPackage.getSelection();
\r
434 if (sel instanceof IStructuredSelection) {
\r
435 Object elem = ((IStructuredSelection) sel).getFirstElement();
\r
436 if (elem instanceof Archive) {
\r
437 return (Archive) elem;
\r
443 private void displayInformation(Archive a) {
\r
445 mPackageText.setText("Please select a package.");
\r
449 mPackageText.setText(""); //$NON-NLS-1$
\r
451 addSectionTitle("Package Description\n");
\r
452 addText(a.getParentPackage().getLongDescription(), "\n\n"); //$NON-NLS-1$
\r
454 Archive aold = mNewToOldArchiveMap.get(a);
\r
455 if (aold != null) {
\r
456 addText(String.format("This update will replace revision %1$s with revision %2$s.\n\n",
\r
457 aold.getParentPackage().getRevision(),
\r
458 a.getParentPackage().getRevision()));
\r
462 addSectionTitle("Archive Description\n");
\r
463 addText(a.getLongDescription(), "\n\n"); //$NON-NLS-1$
\r
465 String license = a.getParentPackage().getLicense();
\r
466 if (license != null) {
\r
467 addSectionTitle("License\n");
\r
468 addText(license.trim(), "\n"); //$NON-NLS-1$
\r
472 private void addText(String...string) {
\r
473 for (String s : string) {
\r
474 mPackageText.append(s);
\r
478 private void addSectionTitle(String string) {
\r
479 String s = mPackageText.getText();
\r
480 int start = (s == null ? 0 : s.length());
\r
481 mPackageText.append(string);
\r
483 StyleRange sr = new StyleRange();
\r
485 sr.length = string.length();
\r
486 sr.fontStyle = SWT.BOLD;
\r
487 sr.underline = true;
\r
488 mPackageText.setStyleRange(sr);
\r
491 private void updateLicenceRadios(Archive a) {
\r
492 if (mInternalLicenseRadioUpdate) {
\r
495 mInternalLicenseRadioUpdate = true;
\r
497 if (mLicenseAcceptAll) {
\r
498 mLicenseRadioAcceptAll.setSelection(true);
\r
499 mLicenseRadioAccept.setSelection(false);
\r
500 mLicenseRadioReject.setSelection(false);
\r
502 mLicenseRadioAcceptAll.setSelection(false);
\r
503 mLicenseRadioAccept.setSelection(mAccepted.contains(a));
\r
504 mLicenseRadioReject.setSelection(mRejected.contains(a));
\r
507 // The install button is enabled if there's at least one
\r
508 // package accepted.
\r
509 mInstallButton.setEnabled(mAccepted.size() > 0);
\r
511 mInternalLicenseRadioUpdate = false;
\r
515 * Callback invoked when one of the radio license buttons is selected.
\r
517 * - accept/refuse: toggle, update item checkbox
\r
518 * - accept all: set accept-all, check all items
\r
520 private void onLicenseRadioSelected() {
\r
521 if (mInternalLicenseRadioUpdate) {
\r
524 mInternalLicenseRadioUpdate = true;
\r
526 Archive a = getSelectedArchive();
\r
527 boolean needUpdate = true;
\r
529 if (!mLicenseAcceptAll && mLicenseRadioAcceptAll.getSelection()) {
\r
530 // Accept all has been switched on. Mark all packages as accepted
\r
531 mLicenseAcceptAll = true;
\r
532 mAccepted.addAll(mNewToOldArchiveMap.keySet());
\r
535 } else if (mLicenseRadioAccept.getSelection()) {
\r
536 // Accept only this one
\r
537 mLicenseAcceptAll = false;
\r
539 mRejected.remove(a);
\r
541 } else if (mLicenseRadioReject.getSelection()) {
\r
542 // Reject only this one
\r
543 mLicenseAcceptAll = false;
\r
544 mAccepted.remove(a);
\r
548 needUpdate = false;
\r
551 mInternalLicenseRadioUpdate = false;
\r
554 if (mLicenseAcceptAll) {
\r
555 mTableViewPackage.refresh();
\r
557 mTableViewPackage.refresh(a);
\r
559 updateLicenceRadios(a);
\r
564 * Callback invoked when a package item is double-clicked in the list.
\r
566 private void onPackageDoubleClick() {
\r
567 Archive a = getSelectedArchive();
\r
569 if (mAccepted.contains(a)) {
\r
570 // toggle from accepted to rejected
\r
571 mAccepted.remove(a);
\r
574 // toggle from rejected or unknown to accepted
\r
576 mRejected.remove(a);
\r
580 mLicenseAcceptAll = false;
\r
581 mTableViewPackage.refresh(a);
\r
582 updateLicenceRadios(a);
\r
585 private class NewArchivesLabelProvider extends LabelProvider {
\r
587 public Image getImage(Object element) {
\r
588 ImageFactory imgFactory = mUpdaterData.getImageFactory();
\r
589 if (imgFactory != null) {
\r
590 if (mAccepted.contains(element)) {
\r
591 return imgFactory.getImageByName("accept_icon16.png");
\r
592 } else if (mRejected.contains(element)) {
\r
593 return imgFactory.getImageByName("reject_icon16.png");
\r
595 return imgFactory.getImageByName("unknown_icon16.png");
\r
597 return super.getImage(element);
\r
601 public String getText(Object element) {
\r
602 if (element instanceof Archive) {
\r
603 return ((Archive) element).getParentPackage().getShortDescription();
\r
605 return super.getText(element);
\r
609 private class NewArchivesContentProvider implements IStructuredContentProvider {
\r
611 public void dispose() {
\r
615 public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
\r
616 // Ignore. The input is always mNewArchives
\r
619 public Object[] getElements(Object inputElement) {
\r
620 return mNewToOldArchiveMap.keySet().toArray();
\r
624 // End of hiding from SWT Designer
\r