/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.sdkuilib.internal.repository.ui; import com.android.sdklib.internal.repository.sources.SdkAddonSource; import com.android.sdklib.internal.repository.sources.SdkSource; import com.android.sdklib.internal.repository.sources.SdkSourceCategory; import com.android.sdklib.internal.repository.sources.SdkSourceProperties; import com.android.sdklib.internal.repository.sources.SdkSources; import com.android.sdklib.internal.repository.sources.SdkSysImgSource; import com.android.sdklib.repository.SdkSysImgConstants; import com.android.sdkuilib.internal.repository.UpdaterBaseDialog; import com.android.sdkuilib.internal.repository.UpdaterData; import com.android.sdkuilib.ui.GridDataBuilder; import com.android.sdkuilib.ui.GridLayoutBuilder; import org.eclipse.jface.dialogs.IInputValidator; import org.eclipse.jface.dialogs.InputDialog; import org.eclipse.jface.viewers.CheckStateChangedEvent; import org.eclipse.jface.viewers.CheckboxTableViewer; import org.eclipse.jface.viewers.ColumnLabelProvider; import org.eclipse.jface.viewers.ICheckStateListener; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.IStructuredContentProvider; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.LabelProvider; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.viewers.TableViewer; import org.eclipse.jface.viewers.TableViewerColumn; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.window.Window; import org.eclipse.swt.SWT; import org.eclipse.swt.events.ControlAdapter; import org.eclipse.swt.events.ControlEvent; import org.eclipse.swt.events.MouseAdapter; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.MessageBox; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.TabFolder; import org.eclipse.swt.widgets.TabItem; import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.TableColumn; import java.util.ArrayList; import java.util.Arrays; /** * Dialog that displays 2 tabs: <br/> * - one tab with the list of extra add-ons sites defined by the user. <br/> * - one tab with the list of 3rd-party add-ons currently available, which the user can * deactivate to prevent from loading them. */ public class AddonSitesDialog extends UpdaterBaseDialog { private final SdkSources mSources; private Table mUserTable; private TableViewer mUserTableViewer; private CheckboxTableViewer mSitesTableViewer; private Button mUserButtonNew; private Button mUserButtonDelete; private Button mUserButtonEdit; private Runnable mSourcesChangeListener; /** * Create the dialog. * * @param parent The parent's shell * @wbp.parser.entryPoint */ public AddonSitesDialog(Shell parent, UpdaterData updaterData) { super(parent, updaterData, "Add-on Sites"); mSources = updaterData.getSources(); assert mSources != null; } /** * Create contents of the dialog. * @wbp.parser.entryPoint */ @Override protected void createContents() { super.createContents(); Shell shell = getShell(); shell.setMinimumSize(new Point(300, 300)); shell.setSize(600, 400); TabFolder tabFolder = new TabFolder(shell, SWT.NONE); GridDataBuilder.create(tabFolder).fill().grab().hSpan(2); TabItem sitesTabItem = new TabItem(tabFolder, SWT.NONE); sitesTabItem.setText("Official Add-on Sites"); createTabOfficialSites(tabFolder, sitesTabItem); TabItem userTabItem = new TabItem(tabFolder, SWT.NONE); userTabItem.setText("User Defined Sites"); createTabUserSites(tabFolder, userTabItem); // placeholder for aligning close button Label label = new Label(shell, SWT.NONE); GridDataBuilder.create(label).hFill().hGrab(); createCloseButton(); } void createTabOfficialSites(TabFolder tabFolder, TabItem sitesTabItem) { Composite root = new Composite(tabFolder, SWT.NONE); sitesTabItem.setControl(root); GridLayoutBuilder.create(root).columns(3); Label label = new Label(root, SWT.NONE); GridDataBuilder.create(label).hGrab().vCenter().hSpan(3); label.setText( "This lets select which official 3rd-party sites you want to load.\n" + "\n" + "These sites are managed by non-Android vendors to provide add-ons and extra packages.\n" + "They are by default all enabled. When you disable one, the SDK Manager will not check the site for new packages." ); mSitesTableViewer = CheckboxTableViewer.newCheckList(root, SWT.BORDER | SWT.FULL_SELECTION); mSitesTableViewer.setContentProvider(new SourcesContentProvider()); Table sitesTable = mSitesTableViewer.getTable(); sitesTable.setToolTipText("Enable 3rd-Party Site"); sitesTable.setLinesVisible(true); sitesTable.setHeaderVisible(true); GridDataBuilder.create(sitesTable).fill().grab().hSpan(3); TableViewerColumn columnViewer = new TableViewerColumn(mSitesTableViewer, SWT.NONE); TableColumn column = columnViewer.getColumn(); column.setResizable(true); column.setWidth(150); column.setText("Name"); columnViewer.setLabelProvider(new ColumnLabelProvider() { @Override public String getText(Object element) { if (element instanceof SdkSource) { String name = ((SdkSource) element).getUiName(); if (name != null) { return name; } return ((SdkSource) element).getShortDescription(); } return super.getText(element); } }); columnViewer = new TableViewerColumn(mSitesTableViewer, SWT.NONE); column = columnViewer.getColumn(); column.setResizable(true); column.setWidth(400); column.setText("URL"); columnViewer.setLabelProvider(new ColumnLabelProvider() { @Override public String getText(Object element) { if (element instanceof SdkSource) { return ((SdkSource) element).getUrl(); } return super.getText(element); } }); mSitesTableViewer.addCheckStateListener(new ICheckStateListener() { @Override public void checkStateChanged(CheckStateChangedEvent event) { on_SitesTableViewer_checkStateChanged(event); } }); // "enable all" and "disable all" buttons under the table Button selectAll = new Button(root, SWT.NONE); selectAll.setText("Enable All"); GridDataBuilder.create(selectAll).hLeft(); selectAll.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent event) { on_SitesTableViewer_selectAll(); } }); // placeholder between both buttons label = new Label(root, SWT.NONE); GridDataBuilder.create(label).hFill().hGrab(); Button deselectAll = new Button(root, SWT.NONE); deselectAll.setText("Disable All"); GridDataBuilder.create(deselectAll).hRight(); deselectAll.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent event) { on_SitesTableViewer_deselectAll(); } }); } void createTabUserSites(TabFolder tabFolder, TabItem userTabItem) { Composite root = new Composite(tabFolder, SWT.NONE); userTabItem.setControl(root); GridLayoutBuilder.create(root).columns(2); Label label = new Label(root, SWT.NONE); GridDataBuilder.create(label).hLeft().vCenter().hSpan(2); label.setText( "This lets you manage a list of user-contributed external add-on sites URLs.\n" + "\n" + "Add-on sites can provide new add-ons and extra packages.\n" + "They cannot provide standard Android platforms, system images or docs.\n" + "Adding a URL here will not allow you to clone an official Android repository." ); mUserTableViewer = new TableViewer(root, SWT.BORDER | SWT.FULL_SELECTION); mUserTableViewer.setContentProvider(new SourcesContentProvider()); mUserTableViewer.addPostSelectionChangedListener(new ISelectionChangedListener() { @Override public void selectionChanged(SelectionChangedEvent event) { on_UserTableViewer_selectionChanged(event); } }); mUserTable = mUserTableViewer.getTable(); mUserTable.setLinesVisible(true); mUserTable.addMouseListener(new MouseAdapter() { @Override public void mouseUp(MouseEvent event) { on_UserTable_mouseUp(event); } }); GridDataBuilder.create(mUserTable).fill().grab().vSpan(5); TableViewerColumn tableViewerColumn = new TableViewerColumn(mUserTableViewer, SWT.NONE); TableColumn userColumnUrl = tableViewerColumn.getColumn(); userColumnUrl.setWidth(100); // Implementation detail: set the label provider on the table viewer *after* associating // a column. This will set the label provider on the column for us. mUserTableViewer.setLabelProvider(new LabelProvider()); mUserButtonNew = new Button(root, SWT.NONE); mUserButtonNew.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { userNewOrEdit(false /*isEdit*/); } }); GridDataBuilder.create(mUserButtonNew).hFill().vCenter(); mUserButtonNew.setText("New..."); mUserButtonEdit = new Button(root, SWT.NONE); mUserButtonEdit.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { userNewOrEdit(true /*isEdit*/); } }); GridDataBuilder.create(mUserButtonEdit).hFill().vCenter(); mUserButtonEdit.setText("Edit..."); mUserButtonDelete = new Button(root, SWT.NONE); mUserButtonDelete.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { on_UserButtonDelete_widgetSelected(e); } }); GridDataBuilder.create(mUserButtonDelete).hFill().vCenter(); mUserButtonDelete.setText("Delete..."); adjustColumnsWidth(mUserTable, userColumnUrl); } @Override protected void close() { if (mSources != null && mSourcesChangeListener != null) { mSources.removeChangeListener(mSourcesChangeListener); } SdkSourceProperties p = new SdkSourceProperties(); p.save(); super.close(); } /** * Adds a listener to adjust the column width when the parent is resized. */ private void adjustColumnsWidth(final Table table, final TableColumn column0) { // Add a listener to resize the column to the full width of the table table.addControlListener(new ControlAdapter() { @Override public void controlResized(ControlEvent e) { Rectangle r = table.getClientArea(); column0.setWidth(r.width * 100 / 100); // 100% } }); } private void userNewOrEdit(final boolean isEdit) { final SdkSource[] knownSources = mSources.getAllSources(); String title = isEdit ? "Edit Add-on Site URL" : "Add Add-on Site URL"; String msg = "Please enter the URL of the addon.xml:"; IStructuredSelection sel = (IStructuredSelection) mUserTableViewer.getSelection(); final String initialValue = !isEdit || sel.isEmpty() ? null : sel.getFirstElement().toString(); if (isEdit && initialValue == null) { // Edit with no actual value is not supposed to happen. Ignore this case. return; } InputDialog dlg = new InputDialog( getShell(), title, msg, initialValue, new IInputValidator() { @Override public String isValid(String newText) { newText = newText == null ? null : newText.trim(); if (newText == null || newText.length() == 0) { return "Error: URL field is empty. Please enter a URL."; } // A URL should have one of the following prefixes if (!newText.startsWith("file://") && //$NON-NLS-1$ !newText.startsWith("ftp://") && //$NON-NLS-1$ !newText.startsWith("http://") && //$NON-NLS-1$ !newText.startsWith("https://")) { //$NON-NLS-1$ return "Error: The URL must start by one of file://, ftp://, http:// or https://"; } if (isEdit && newText.equals(initialValue)) { // Edited value hasn't changed. This isn't an error. return null; } // Reject URLs that are already in the source list. // URLs are generally case-insensitive (except for file:// where it all depends // on the current OS so we'll ignore this case.) for (SdkSource s : knownSources) { if (newText.equalsIgnoreCase(s.getUrl())) { return "Error: This site is already listed."; } } return null; } }); if (dlg.open() == Window.OK) { String url = dlg.getValue().trim(); if (!url.equals(initialValue)) { if (isEdit && initialValue != null) { // Remove the old value before we add the new one, which is we just // asserted will be different. for (SdkSource source : mSources.getSources(SdkSourceCategory.USER_ADDONS)) { if (initialValue.equals(source.getUrl())) { mSources.remove(source); break; } } } // create the source, store it and update the list SdkSource newSource; // use url suffix to decide whether this is a SysImg or Addon; // see SdkSources.loadUserAddons() for another check like this if (url.endsWith(SdkSysImgConstants.URL_DEFAULT_FILENAME)) { newSource = new SdkSysImgSource(url, null/*uiName*/); } else { newSource = new SdkAddonSource(url, null/*uiName*/); } mSources.add(SdkSourceCategory.USER_ADDONS, newSource); setReturnValue(true); // notify sources change listeners. This will invoke our own loadUserUrlsList(). mSources.notifyChangeListeners(); // select the new source IStructuredSelection newSel = new StructuredSelection(newSource); mUserTableViewer.setSelection(newSel, true /*reveal*/); } } } private void on_UserButtonDelete_widgetSelected(SelectionEvent e) { IStructuredSelection sel = (IStructuredSelection) mUserTableViewer.getSelection(); String selectedUrl = sel.isEmpty() ? null : sel.getFirstElement().toString(); if (selectedUrl == null) { return; } MessageBox mb = new MessageBox(getShell(), SWT.YES | SWT.NO | SWT.ICON_QUESTION | SWT.APPLICATION_MODAL); mb.setText("Delete add-on site"); mb.setMessage(String.format("Do you want to delete the URL %1$s?", selectedUrl)); if (mb.open() == SWT.YES) { for (SdkSource source : mSources.getSources(SdkSourceCategory.USER_ADDONS)) { if (selectedUrl.equals(source.getUrl())) { mSources.remove(source); setReturnValue(true); mSources.notifyChangeListeners(); break; } } } } private void on_UserTable_mouseUp(MouseEvent event) { Point p = new Point(event.x, event.y); if (mUserTable.getItem(p) == null) { mUserTable.deselectAll(); on_UserTableViewer_selectionChanged(null /*event*/); } } private void on_UserTableViewer_selectionChanged(SelectionChangedEvent event) { ISelection sel = mUserTableViewer.getSelection(); mUserButtonDelete.setEnabled(!sel.isEmpty()); mUserButtonEdit.setEnabled(!sel.isEmpty()); } private void on_SitesTableViewer_checkStateChanged(CheckStateChangedEvent event) { Object element = event.getElement(); if (element instanceof SdkSource) { SdkSource source = (SdkSource) element; boolean isChecked = event.getChecked(); if (source.isEnabled() != isChecked) { setReturnValue(true); source.setEnabled(isChecked); mSources.notifyChangeListeners(); } } } private void on_SitesTableViewer_selectAll() { for (Object item : (Object[]) mSitesTableViewer.getInput()) { if (!mSitesTableViewer.getChecked(item)) { mSitesTableViewer.setChecked(item, true); on_SitesTableViewer_checkStateChanged( new CheckStateChangedEvent(mSitesTableViewer, item, true)); } } } private void on_SitesTableViewer_deselectAll() { for (Object item : (Object[]) mSitesTableViewer.getInput()) { if (mSitesTableViewer.getChecked(item)) { mSitesTableViewer.setChecked(item, false); on_SitesTableViewer_checkStateChanged( new CheckStateChangedEvent(mSitesTableViewer, item, false)); } } } @Override protected void postCreate() { // A runnable to initially load and then update the user urls & sites lists. final Runnable updateInUiThread = new Runnable() { @Override public void run() { loadUserUrlsList(); loadSiteUrlsList(); } }; // A listener that runs when the sources have changed. // This is most likely called on a worker thread. mSourcesChangeListener = new Runnable() { @Override public void run() { Shell shell = getShell(); if (shell != null) { Display display = shell.getDisplay(); if (display != null) { display.syncExec(updateInUiThread); } } } }; mSources.addChangeListener(mSourcesChangeListener); // initialize the list updateInUiThread.run(); } private void loadUserUrlsList() { SdkSource[] knownSources = mSources.getSources(SdkSourceCategory.USER_ADDONS); Arrays.sort(knownSources); ISelection oldSelection = mUserTableViewer.getSelection(); mUserTableViewer.setInput(knownSources); mUserTableViewer.refresh(); // initialize buttons' state that depend on the list on_UserTableViewer_selectionChanged(null /*event*/); if (oldSelection != null && !oldSelection.isEmpty()) { mUserTableViewer.setSelection(oldSelection, true /*reveal*/); } } private void loadSiteUrlsList() { SdkSource[] knownSources = mSources.getSources(SdkSourceCategory.ADDONS_3RD_PARTY); Arrays.sort(knownSources); ISelection oldSelection = mSitesTableViewer.getSelection(); mSitesTableViewer.setInput(knownSources); mSitesTableViewer.refresh(); if (oldSelection != null && !oldSelection.isEmpty()) { mSitesTableViewer.setSelection(oldSelection, true /*reveal*/); } // Check the sources which are currently enabled. ArrayList<SdkSource> disabled = new ArrayList<SdkSource>(knownSources.length); for (SdkSource source : knownSources) { if (source.isEnabled()) { disabled.add(source); } } mSitesTableViewer.setCheckedElements(disabled.toArray()); } private static class SourcesContentProvider implements IStructuredContentProvider { @Override public void dispose() { // pass } @Override public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { // pass } @Override public Object[] getElements(Object inputElement) { if (inputElement instanceof SdkSource[]) { return (Object[]) inputElement; } else { return new Object[0]; } } } }