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
20 import com.android.sdklib.internal.repository.Archive;
\r
21 import com.android.sdklib.internal.repository.IDescription;
\r
22 import com.android.sdklib.internal.repository.Package;
\r
23 import com.android.sdklib.internal.repository.RepoSource;
\r
24 import com.android.sdkuilib.repository.UpdaterWindow.ISdkListener;
\r
26 import org.eclipse.jface.dialogs.IInputValidator;
\r
27 import org.eclipse.jface.dialogs.InputDialog;
\r
28 import org.eclipse.jface.dialogs.MessageDialog;
\r
29 import org.eclipse.jface.viewers.CheckStateChangedEvent;
\r
30 import org.eclipse.jface.viewers.CheckboxTreeViewer;
\r
31 import org.eclipse.jface.viewers.ICheckStateListener;
\r
32 import org.eclipse.jface.viewers.ISelection;
\r
33 import org.eclipse.jface.viewers.ITreeContentProvider;
\r
34 import org.eclipse.jface.viewers.ITreeSelection;
\r
35 import org.eclipse.jface.window.Window;
\r
36 import org.eclipse.swt.SWT;
\r
37 import org.eclipse.swt.events.ControlAdapter;
\r
38 import org.eclipse.swt.events.ControlEvent;
\r
39 import org.eclipse.swt.events.SelectionAdapter;
\r
40 import org.eclipse.swt.events.SelectionEvent;
\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.Group;
\r
47 import org.eclipse.swt.widgets.Label;
\r
48 import org.eclipse.swt.widgets.Tree;
\r
49 import org.eclipse.swt.widgets.TreeColumn;
\r
51 import java.util.ArrayList;
\r
54 public class RemotePackagesPage extends Composite implements ISdkListener {
\r
56 private final UpdaterData mUpdaterData;
\r
58 private CheckboxTreeViewer mTreeViewerSources;
\r
59 private Tree mTreeSources;
\r
60 private TreeColumn mColumnSource;
\r
61 private Button mUpdateOnlyCheckBox;
\r
62 private Group mDescriptionContainer;
\r
63 private Button mAddSiteButton;
\r
64 private Button mDeleteSiteButton;
\r
65 private Label mPlaceholder3;
\r
66 private Button mRefreshButton;
\r
67 private Button mInstallSelectedButton;
\r
68 private Label mDescriptionLabel;
\r
73 * Create the composite.
\r
74 * @param parent The parent of the composite.
\r
75 * @param updaterData An instance of {@link UpdaterData}.
\r
77 RemotePackagesPage(Composite parent, UpdaterData updaterData) {
\r
78 super(parent, SWT.BORDER);
\r
80 mUpdaterData = updaterData;
\r
82 createContents(this);
\r
83 postCreate(); //$hide$
\r
86 private void createContents(Composite parent) {
\r
87 parent.setLayout(new GridLayout(5, false));
\r
89 mTreeViewerSources = new CheckboxTreeViewer(parent, SWT.BORDER);
\r
90 mTreeViewerSources.addCheckStateListener(new ICheckStateListener() {
\r
91 public void checkStateChanged(CheckStateChangedEvent event) {
\r
92 onTreeCheckStateChanged(event); //$hide$
\r
95 mTreeSources = mTreeViewerSources.getTree();
\r
96 mTreeSources.addSelectionListener(new SelectionAdapter() {
\r
98 public void widgetSelected(SelectionEvent e) {
\r
99 onTreeSelected(); //$hide$
\r
102 mTreeSources.setHeaderVisible(true);
\r
103 mTreeSources.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 5, 1));
\r
105 mColumnSource = new TreeColumn(mTreeSources, SWT.NONE);
\r
106 mColumnSource.setWidth(289);
\r
107 mColumnSource.setText("Sources, Packages and Archives");
\r
109 Composite composite = new Composite(parent, SWT.NONE);
\r
110 composite.setLayoutData(
\r
111 new GridData(SWT.FILL, SWT.BEGINNING, false, false, 5, 1));
\r
113 composite.setLayout(gl = new GridLayout(2, false));
\r
114 gl.marginHeight = gl.marginWidth = 0;
\r
115 // add an empty composite
\r
116 Composite spacer = new Composite(composite, SWT.NONE);
\r
118 spacer.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
\r
121 mUpdateOnlyCheckBox = new Button(composite, SWT.CHECK);
\r
122 mUpdateOnlyCheckBox.setText("Display updates only");
\r
123 mUpdateOnlyCheckBox.setSelection(mUpdaterData.getSettingsController().getShowUpdateOnly());
\r
124 mUpdateOnlyCheckBox.addSelectionListener(new SelectionAdapter() {
\r
126 public void widgetSelected(SelectionEvent arg0) {
\r
127 onShowUpdateOnly(); //$hide$
\r
131 mDescriptionContainer = new Group(parent, SWT.NONE);
\r
132 mDescriptionContainer.setLayout(new GridLayout(1, false));
\r
133 mDescriptionContainer.setText("Description");
\r
134 mDescriptionContainer.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false, 5, 1));
\r
136 mDescriptionLabel = new Label(mDescriptionContainer, SWT.NONE);
\r
137 mDescriptionLabel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
\r
138 mDescriptionLabel.setText("Line1\nLine2\nLine3");
\r
140 mAddSiteButton = new Button(parent, SWT.NONE);
\r
141 mAddSiteButton.addSelectionListener(new SelectionAdapter() {
\r
143 public void widgetSelected(SelectionEvent e) {
\r
144 onAddSiteSelected(); //$hide$
\r
147 mAddSiteButton.setText("Add Site...");
\r
149 mDeleteSiteButton = new Button(parent, SWT.NONE);
\r
150 mDeleteSiteButton.addSelectionListener(new SelectionAdapter() {
\r
152 public void widgetSelected(SelectionEvent e) {
\r
153 onRemoveSiteSelected(); //$hide$
\r
156 mDeleteSiteButton.setText("Delete Site...");
\r
158 mPlaceholder3 = new Label(parent, SWT.NONE);
\r
159 mPlaceholder3.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, false, 1, 1));
\r
161 mRefreshButton = new Button(parent, SWT.NONE);
\r
162 mRefreshButton.addSelectionListener(new SelectionAdapter() {
\r
164 public void widgetSelected(SelectionEvent e) {
\r
165 onRefreshSelected(); //$hide$
\r
168 mRefreshButton.setText("Refresh");
\r
170 mInstallSelectedButton = new Button(parent, SWT.NONE);
\r
171 mInstallSelectedButton.addSelectionListener(new SelectionAdapter() {
\r
173 public void widgetSelected(SelectionEvent e) {
\r
174 onInstallSelectedArchives(); //$hide$
\r
177 mInstallSelectedButton.setText("Install Selected");
\r
181 public void dispose() {
\r
182 mUpdaterData.removeListener(this);
\r
187 protected void checkSubclass() {
\r
188 // Disable the check that prevents subclassing of SWT components
\r
191 // -- Start of internal part ----------
\r
192 // Hide everything down-below from SWT designer
\r
196 * Called by the constructor right after {@link #createContents(Composite)}.
\r
198 private void postCreate() {
\r
199 mUpdaterData.addListeners(this);
\r
200 adjustColumnsWidth();
\r
201 updateButtonsState();
\r
205 * Adds a listener to adjust the columns width when the parent is resized.
\r
207 * If we need something more fancy, we might want to use this:
\r
208 * http://dev.eclipse.org/viewcvs/index.cgi/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet77.java?view=co
\r
210 private void adjustColumnsWidth() {
\r
211 // Add a listener to resize the column to the full width of the table
\r
212 ControlAdapter resizer = new ControlAdapter() {
\r
214 public void controlResized(ControlEvent e) {
\r
215 Rectangle r = mTreeSources.getClientArea();
\r
216 mColumnSource.setWidth(r.width);
\r
220 mTreeSources.addControlListener(resizer);
\r
221 resizer.controlResized(null);
\r
225 * Called when an item in the package table viewer is selected.
\r
226 * If the items is an {@link IDescription} (as it should), this will display its long
\r
227 * description in the description area. Otherwise when the item is not of the expected
\r
228 * type or there is no selection, it empties the description area.
\r
230 private void onTreeSelected() {
\r
231 updateButtonsState();
\r
233 ISelection sel = mTreeViewerSources.getSelection();
\r
234 if (sel instanceof ITreeSelection) {
\r
235 Object elem = ((ITreeSelection) sel).getFirstElement();
\r
236 if (elem instanceof IDescription) {
\r
237 mDescriptionLabel.setText(((IDescription) elem).getLongDescription());
\r
238 mDescriptionContainer.layout(true);
\r
242 mDescriptionLabel.setText(""); //$NON-NLS1-$
\r
246 * Handle checking and unchecking of the tree items.
\r
248 * When unchecking, all sub-tree items checkboxes are cleared too.
\r
249 * When checking a source, all of its packages are checked too.
\r
250 * When checking a package, only its compatible archives are checked.
\r
252 private void onTreeCheckStateChanged(CheckStateChangedEvent event) {
\r
253 updateButtonsState();
\r
255 boolean b = event.getChecked();
\r
256 Object elem = event.getElement(); // Will be Archive or Package or RepoSource
\r
258 assert event.getSource() == mTreeViewerSources;
\r
260 // when deselecting, we just deselect all children too
\r
262 mTreeViewerSources.setSubtreeChecked(elem, b);
\r
266 ITreeContentProvider provider =
\r
267 (ITreeContentProvider) mTreeViewerSources.getContentProvider();
\r
269 // When selecting, we want to only select compatible archives.
\r
270 if (elem instanceof RepoSource) {
\r
271 mTreeViewerSources.setExpandedState(elem, true);
\r
272 for (Object pkg : provider.getChildren(elem)) {
\r
273 mTreeViewerSources.setChecked(pkg, true);
\r
274 selectCompatibleArchives(pkg, provider);
\r
276 } else if (elem instanceof Package) {
\r
277 selectCompatibleArchives(elem, provider);
\r
281 private void selectCompatibleArchives(Object pkg, ITreeContentProvider provider) {
\r
282 mTreeViewerSources.setExpandedState(pkg, true);
\r
283 for (Object archive : provider.getChildren(pkg)) {
\r
284 if (archive instanceof Archive) {
\r
285 mTreeViewerSources.setChecked(archive, ((Archive) archive).isCompatible());
\r
290 private void onShowUpdateOnly() {
\r
291 mUpdaterData.getSettingsController().setShowUpdateOnly(mUpdateOnlyCheckBox.getSelection());
\r
292 mTreeViewerSources.refresh();
\r
295 private void onInstallSelectedArchives() {
\r
296 ArrayList<Archive> archives = new ArrayList<Archive>();
\r
297 for (Object element : mTreeViewerSources.getCheckedElements()) {
\r
298 if (element instanceof Archive) {
\r
299 archives.add((Archive) element);
\r
303 if (mUpdaterData != null) {
\r
304 mUpdaterData.updateOrInstallAll(archives);
\r
308 private void onAddSiteSelected() {
\r
310 final RepoSource[] knowSources = mUpdaterData.getSources().getSources();
\r
311 String title = "Add Site URL";
\r
312 String msg = "Please enter the URL of the repository.xml for the new site:";
\r
314 InputDialog dlg = new InputDialog(getShell(), title, msg, null, new IInputValidator() {
\r
315 public String isValid(String newText) {
\r
317 if (newText == null || newText.length() == 0) {
\r
318 return "Please enter an URL.";
\r
321 // A URL should have one of the following prefixes
\r
322 if (!newText.startsWith("file://") &&
\r
323 !newText.startsWith("ftp://") &&
\r
324 !newText.startsWith("http://") &&
\r
325 !newText.startsWith("https://")) {
\r
326 return "The URL must start by one of file://, ftp://, http:// or https://";
\r
329 // Reject URLs that are already in the source list
\r
330 for (RepoSource s : knowSources) {
\r
331 if (newText.equals(s.getUrl())) {
\r
332 return "This site is already listed.";
\r
340 if (dlg.open() == Window.OK) {
\r
341 String url = dlg.getValue();
\r
342 mUpdaterData.getSources().add(new RepoSource(url, true /*userSource*/));
\r
343 onRefreshSelected();
\r
347 private void onRemoveSiteSelected() {
\r
348 boolean changed = false;
\r
350 ISelection sel = mTreeViewerSources.getSelection();
\r
351 if (mUpdaterData != null && sel instanceof ITreeSelection) {
\r
352 for (Object c : ((ITreeSelection) sel).toList()) {
\r
353 if (c instanceof RepoSource && ((RepoSource) c).isUserSource()) {
\r
354 RepoSource source = (RepoSource) c;
\r
356 String title = "Delete Site?";
\r
358 String msg = String.format("Are you sure you want to delete the site '%1$s'?",
\r
361 if (MessageDialog.openQuestion(getShell(), title, msg)) {
\r
362 mUpdaterData.getSources().remove(source);
\r
370 onRefreshSelected();
\r
374 private void onRefreshSelected() {
\r
375 if (mUpdaterData != null) {
\r
376 mUpdaterData.refreshSources(false /*forceFetching*/);
\r
378 mTreeViewerSources.refresh();
\r
379 updateButtonsState();
\r
382 public void onSdkChange() {
\r
383 RepoSourcesAdapter sources = mUpdaterData.getSourcesAdapter();
\r
384 mTreeViewerSources.setContentProvider(sources.getContentProvider());
\r
385 mTreeViewerSources.setLabelProvider( sources.getLabelProvider());
\r
386 mTreeViewerSources.setInput(sources);
\r
390 private void updateButtonsState() {
\r
391 // We install archives, so there should be at least one checked archive.
\r
392 // Having sites or packages checked does not count.
\r
393 boolean hasCheckedArchive = false;
\r
394 Object[] checked = mTreeViewerSources.getCheckedElements();
\r
395 if (checked != null) {
\r
396 for (Object c : checked) {
\r
397 if (c instanceof Archive) {
\r
398 hasCheckedArchive = true;
\r
404 // Is there a selected site Source?
\r
405 boolean hasSelectedUserSource = false;
\r
406 ISelection sel = mTreeViewerSources.getSelection();
\r
407 if (sel instanceof ITreeSelection) {
\r
408 for (Object c : ((ITreeSelection) sel).toList()) {
\r
409 if (c instanceof RepoSource &&
\r
410 ((RepoSource) c).isUserSource()) {
\r
411 hasSelectedUserSource = true;
\r
417 mAddSiteButton.setEnabled(true);
\r
418 mDeleteSiteButton.setEnabled(hasSelectedUserSource);
\r
419 mRefreshButton.setEnabled(true);
\r
420 mInstallSelectedButton.setEnabled(hasCheckedArchive);
\r
423 // End of hiding from SWT Designer
\r