/*
* Copyright (c) 2012, the Dart project authors.
*
* Licensed under the Eclipse Public License v1.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.eclipse.org/legal/epl-v10.html
*
* 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.google.dart.tools.ui.dialogs;
import com.google.common.base.Strings;
import com.google.dart.tools.ui.internal.util.ExternalBrowserUtil;
import com.google.dart.tools.ui.themes.Fonts;
import com.google.dart.tools.update.core.Revision;
import com.google.dart.tools.update.core.UpdateAdapter;
import com.google.dart.tools.update.core.UpdateCore;
import com.google.dart.tools.update.core.UpdateManager;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.dialogs.IInputValidator;
import org.eclipse.jface.dialogs.InputDialog;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.window.Window;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Link;
/**
* Contributes an update status label and update action button to the {@link AboutDartDialog}.
* TODO(pquitslund): this implementation and UX is provisional and under active development
*/
public class UpdateStatusControl extends UpdateAdapter implements DisposeListener {
private class UpdateAction extends Action {
public UpdateAction(String text) {
super(text);
}
public void runWithSelectionEvent(SelectionEvent event) {
run();
};
}
private static final String WHATS_NEW_LINK_TEXT = " <a>What's new?</a>";
private static final String NEW_VERSION_AVAILABLE_MSG = "A new version {0} is ready to install.";
private Link updateStatusLabel;
private Button updateStatusButton;
private UpdateAction updateAction;
private Font regularFont;
private Font italicFont;
private UpdateAction applyUpdateAction = new UpdateAction("Apply Update...") {
@Override
public void run() {
UpdateCore.getUpdateManager().scheduleInstall();
}
};
private UpdateAction downloadUpdateAction = new UpdateAction("Download Update...") {
@Override
public void run() {
UpdateCore.getUpdateManager().scheduleDownload(latestAvailableRevision);
}
@Override
public void runWithSelectionEvent(SelectionEvent event) {
if (event.stateMask == SWT.SHIFT) {
Revision revision = promptForRevision(latestAvailableRevision);
if (revision == null) {
return;
}
latestAvailableRevision = revision;
}
super.runWithSelectionEvent(event);
};
};
private UpdateAction checkFordUpdatesAction = new UpdateAction("Check for Update...") {
@Override
public void run() {
UpdateCore.getUpdateManager().scheduleUpdateCheck();
}
};
private Revision latestAvailableRevision;
private final Color backgroundColor;
private final Point margin;
private final boolean isCentered;
public UpdateStatusControl(Composite parent, Color backgroundColor, Point margin,
boolean isCentered) {
this.backgroundColor = backgroundColor;
this.margin = margin;
this.isCentered = isCentered;
createControl(parent);
cacheFonts();
//pad the initial status message to ensure it's wide enough to display new version available text
setStatus(
padToMatch(
"Checking for updates...",
bindRevision(NEW_VERSION_AVAILABLE_MSG + WHATS_NEW_LINK_TEXT, "000000")),
italicFont);
setActionDisabled(checkFordUpdatesAction);
UpdateManager updateManager = UpdateCore.getUpdateManager();
updateManager.addListener(this);
if (updateManager.isDownloadingUpdate()) {
downloadStarted();
} else {
updateManager.scheduleUpdateCheck();
}
parent.addDisposeListener(this);
}
@Override
public void checkComplete() {
UpdateCore.logInfo("UpdateStatusControl.checkComplete()");
asyncExec(new Runnable() {
@Override
public void run() {
// Default status to be overwritten by future state changes
setStatus("Dart Editor is up to date.", regularFont);
setActionDisabled(checkFordUpdatesAction);
}
});
}
@Override
public void checkFailed(final String message) {
UpdateCore.logInfo("UpdateStatusControl.checkFailed()");
asyncExec(new Runnable() {
@Override
public void run() {
setStatus(message, regularFont);
setActionEnabled(checkFordUpdatesAction);
}
});
}
@Override
public void checkStarted() {
asyncExec(new Runnable() {
@Override
public void run() {
setStatus("Checking for updates...", italicFont);
setActionDisabled(downloadUpdateAction);
}
});
}
@Override
public void downloadCancelled() {
asyncExec(new Runnable() {
@Override
public void run() {
setStatus("Download failed. Retry?", regularFont);
setActionEnabled(downloadUpdateAction);
}
});
}
@Override
public void downloadComplete() {
UpdateCore.logInfo("UpdateStatusControl.downloadComplete()");
asyncExec(new Runnable() {
@Override
public void run() {
if (latestAvailableRevision == null) {
latestAvailableRevision = UpdateManager.getInstance().getLatestStagedUpdate();
}
if (latestAvailableRevision != null) {
setStatus(bindRevision(NEW_VERSION_AVAILABLE_MSG, latestAvailableRevision), regularFont);
setActionEnabled(applyUpdateAction);
}
}
});
}
@Override
public void downloadStarted() {
UpdateCore.logInfo("UpdateStatusControl.downloadStarted()");
asyncExec(new Runnable() {
@Override
public void run() {
setStatus("Downloading update...", italicFont);
setActionDisabled(downloadUpdateAction);
}
});
}
@Override
public void updateAvailable(final Revision revision) {
this.latestAvailableRevision = revision;
UpdateCore.logInfo("UpdateStatusControl.updateAvailable() => " + latestAvailableRevision);
asyncExec(new Runnable() {
@Override
public void run() {
String link = "";
String url = UpdateCore.getUpdateUrl();
//sanity check in case a custom URL is being used
if (url != null && url.contains("/channels/dev/")) {
link = WHATS_NEW_LINK_TEXT;
}
setStatus(
bindRevision(NEW_VERSION_AVAILABLE_MSG + link, latestAvailableRevision),
regularFont);
setActionEnabled(downloadUpdateAction);
}
});
}
@Override
public void updateStaged() {
UpdateCore.logInfo("UpdateStatusControl.updateStaged()");
asyncExec(new Runnable() {
@Override
public void run() {
setStatus("An update is ready to install", regularFont);
setActionEnabled(applyUpdateAction);
}
});
}
@Override
public void widgetDisposed(DisposeEvent e) {
UpdateCore.getUpdateManager().removeListener(this);
}
private void asyncExec(Runnable runnable) {
updateStatusLabel.getDisplay().asyncExec(runnable);
}
private String bindRevision(String msg, Object revision) {
return NLS.bind(msg, "[" + revision + "]");
}
private void cacheFonts() {
regularFont = updateStatusLabel.getFont();
italicFont = Fonts.getItalicFont(regularFont);
}
private void createControl(final Composite parent) {
Composite comp = new Composite(parent, SWT.NONE);
GridDataFactory.fillDefaults().grab(true, false).align(SWT.FILL, SWT.BEGINNING).applyTo(comp);
GridLayoutFactory.fillDefaults().numColumns(isCentered ? 1 : 2).margins(margin.x, margin.y).applyTo(
comp);
updateStatusLabel = new Link(comp, SWT.NO_FOCUS);
if (backgroundColor != null) {
comp.setBackground(backgroundColor);
updateStatusLabel.setBackground(backgroundColor);
}
updateStatusLabel.addSelectionListener(new SelectionListener() {
@Override
public void widgetDefaultSelected(SelectionEvent e) {
widgetSelected(e);
}
@Override
public void widgetSelected(SelectionEvent e) {
ExternalBrowserUtil.openInExternalBrowser(UpdateCore.getChangeLogUrl());
}
});
GridDataFactory.fillDefaults().align(isCentered ? SWT.CENTER : SWT.FILL, SWT.CENTER).grab(
true,
false).applyTo(updateStatusLabel);
updateStatusButton = new Button(comp, SWT.PUSH);
updateStatusButton.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE));
GridDataFactory.fillDefaults().align(SWT.CENTER, SWT.CENTER).indent(0, 3).applyTo(
updateStatusButton);
updateStatusButton.addSelectionListener(new SelectionListener() {
@Override
public void widgetDefaultSelected(SelectionEvent e) {
performAction(e);
}
@Override
public void widgetSelected(SelectionEvent e) {
performAction(e);
}
});
}
private String padToMatch(String original, String toMatch) {
int pad = toMatch.length() - original.length();
if (pad > 0) {
return original.concat(Strings.repeat(" ", pad + 5));
}
return original;
}
private void performAction(SelectionEvent e) {
if (updateAction != null) {
updateAction.runWithSelectionEvent(e);
}
}
private Revision promptForRevision(Revision originalRevision) {
InputDialog inputDialog = new InputDialog(
null,
"Download Revision",
"FOR DEVELOPMENT USE ONLY" + System.getProperty("line.separator")
+ " Enter the revision to be downloaded and installed",
originalRevision.toString(),
new IInputValidator() {
@Override
public String isValid(String newText) {
if (newText == null || newText.length() == 0) {
return "Enter a revision # or click Cancel";
}
try {
Integer.valueOf(newText);
} catch (NumberFormatException e) {
return "Invalid revision #";
}
return null;
}
});
if (inputDialog.open() != Window.OK) {
return null;
}
return new Revision(Integer.valueOf(inputDialog.getValue()));
}
private void setAction(UpdateAction action, boolean enabled) {
this.updateAction = action;
updateStatusButton.setText(action.getText());
updateStatusButton.setEnabled(enabled);
updateStatusButton.getParent().layout();
}
private void setActionDisabled(UpdateAction action) {
setAction(action, false);
}
private void setActionEnabled(UpdateAction action) {
setAction(action, true);
}
private void setStatus(String text, Font font) {
// Guard against null values during initialization
if (text == null) {
text = "";
}
updateStatusLabel.setText(text);
updateStatusLabel.setFont(font);
updateStatusLabel.update();
}
}