Java tutorial
/* * Copyright (c) 2015 Eike Stepper (Berlin, Germany) and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Eike Stepper - initial API and implementation * Yatta Solutions - [466264] Enhance UX in simple installer */ package org.eclipse.oomph.setup.internal.installer; import org.eclipse.oomph.internal.ui.AccessUtil; import org.eclipse.oomph.internal.ui.FlatButton; import org.eclipse.oomph.internal.ui.ImageHoverButton; import org.eclipse.oomph.internal.ui.ToggleSwitchButton; import org.eclipse.oomph.p2.core.BundlePool; import org.eclipse.oomph.p2.core.P2Util; import org.eclipse.oomph.p2.core.ProfileTransaction.Resolution; import org.eclipse.oomph.p2.internal.ui.AgentManagerDialog; import org.eclipse.oomph.setup.Index; import org.eclipse.oomph.setup.Product; import org.eclipse.oomph.setup.ProductCatalog; import org.eclipse.oomph.setup.User; import org.eclipse.oomph.setup.internal.core.util.CatalogManager; import org.eclipse.oomph.setup.internal.core.util.ECFURIHandlerImpl; import org.eclipse.oomph.setup.internal.installer.SimpleMessageOverlay.ControlRelocator; import org.eclipse.oomph.setup.ui.SetupUIPlugin; import org.eclipse.oomph.setup.ui.wizards.SetupWizard.SelectionMemento; import org.eclipse.oomph.ui.ErrorDialog; import org.eclipse.oomph.ui.UIUtil; import org.eclipse.oomph.util.ExceptionHandler; import org.eclipse.oomph.util.IOExceptionWithCause; import org.eclipse.oomph.util.OS; import org.eclipse.oomph.util.OomphPlugin.BundleFile; import org.eclipse.oomph.util.OomphPlugin.Preference; import org.eclipse.oomph.util.StringUtil; import org.eclipse.core.runtime.CoreException; import org.eclipse.jface.dialogs.IDialogConstants; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.StackLayout; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Drawable; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Label; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; import java.io.IOException; import java.net.URI; import java.util.List; import java.util.Stack; /** * @author Eike Stepper */ public final class SimpleInstallerDialog extends AbstractSimpleDialog implements InstallerUI { private static final String CATALOGS_MENU_ITEM_TEXT = ProductCatalogsDialog.TITLE.toUpperCase() + StringUtil.HORIZONTAL_ELLIPSIS; private static final String BUNDLE_POOLS_MENU_ITEM_TEXT = "BUNDLE POOLS" + StringUtil.HORIZONTAL_ELLIPSIS; private static final String UPDATE_MENU_ITEM_TEXT = "UPDATE"; private static final String ADVANCED_MENU_ITEM_TEXT = "ADVANCED MODE..."; private static final String ABOUT_MENU_ITEM_TEXT = "ABOUT"; private static final String EXIT_MENU_ITEM_TEXT = "EXIT"; private static final Preference PREF_POOL_ENABLED = SetupInstallerPlugin.INSTANCE .getConfigurationPreference("poolEnabled"); private static Font defaultFont; private static Point defaultSize; private static String css; private static String pageTemplate; private static String productTemplate; private static String productTemplateLarge; private final Installer installer; private final boolean restarted; private final CatalogManager catalogManager; private final Stack<SimpleInstallerPage> pageStack = new Stack<SimpleInstallerPage>(); private Composite stack; private StackLayout stackLayout; private SimpleProductPage productPage; private SimpleVariablePage variablePage; private SimpleReadmePage readmePage; private SimpleInstallationLogPage installationLogPage; private SimpleKeepInstallerPage keepInstallerPage; private SimpleInstallerMenu installerMenu; private SimpleInstallerMenuButton menuButton; private boolean poolEnabled; private BundlePool pool; private Resolution updateResolution; private SimpleMessageOverlay currentMessage; private ToggleSwitchButton bundlePoolSwitch; public SimpleInstallerDialog(Display display, final Installer installer, boolean restarted) { super(display, OS.INSTANCE.isMac() ? SWT.TOOL : SWT.NO_TRIM, getDefaultSize(display).x, getDefaultSize(display).y); setMinimumSize(385, 75); this.installer = installer; this.restarted = restarted; catalogManager = installer.getCatalogManager(); } @Override protected void createUI(Composite titleComposite) { Composite exitMenuButtonContainer = new Composite(titleComposite, SWT.NONE); exitMenuButtonContainer.setLayout(UIUtil.createGridLayout(1)); exitMenuButtonContainer.setLayoutData( GridDataFactory.swtDefaults().grab(false, true).align(SWT.CENTER, SWT.FILL).create()); FlatButton exitButton = new ImageHoverButton(exitMenuButtonContainer, SWT.PUSH, SetupInstallerPlugin.INSTANCE.getSWTImage("simple/exit.png"), SetupInstallerPlugin.INSTANCE.getSWTImage("simple/exit_hover.png")); exitButton.setShowButtonDownState(false); exitButton.setLayoutData(GridDataFactory.swtDefaults().align(SWT.CENTER, SWT.BEGINNING).create()); exitButton.setToolTipText("Exit"); exitButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { exitSelected(); } }); menuButton = new SimpleInstallerMenuButton(exitMenuButtonContainer); menuButton.setLayoutData(GridDataFactory.swtDefaults().grab(false, true).align(SWT.CENTER, SWT.BEGINNING) .indent(11, 0).create()); menuButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { // Don't use the event, it can be null! toggleMenu(); } }); stackLayout = new StackLayout(); stack = new Composite(this, SWT.NONE); stack.setLayoutData(new GridData(GridData.FILL, GridData.FILL, true, true)); stack.setLayout(stackLayout); SelectionMemento selectionMemento = installer.getSelectionMemento(); productPage = new SimpleProductPage(stack, this, selectionMemento); variablePage = new SimpleVariablePage(stack, this, selectionMemento); if (UIUtil.isBrowserAvailable()) { readmePage = new SimpleReadmePage(stack, this); } installationLogPage = new SimpleInstallationLogPage(stack, this); keepInstallerPage = new SimpleKeepInstallerPage(stack, this); switchToPage(productPage); Display display = getDisplay(); if (!restarted) { Thread updateSearcher = new UpdateSearcher(display); updateSearcher.start(); } display.timerExec(500, new Runnable() { public void run() { installer.getResourceSet().getLoadOptions().put(ECFURIHandlerImpl.OPTION_CACHE_HANDLING, ECFURIHandlerImpl.CacheHandling.CACHE_WITHOUT_ETAG_CHECKING); installer.loadIndex(); } }); // Initialize menu. getInstallerMenu(); final PropertyChangeListener catalogManagerListener = new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent evt) { if (CatalogManager.PROPERTY_INDEX.equals(evt.getPropertyName())) { indexLoaded((Index) evt.getNewValue()); } } }; catalogManager.addPropertyChangeListener(catalogManagerListener); addDisposeListener(new DisposeListener() { public void widgetDisposed(DisposeEvent e) { catalogManager.removePropertyChangeListener(catalogManagerListener); } }); enablePool(PREF_POOL_ENABLED.get(true)); updateAvailable(false); } private void indexLoaded(Index index) { @SuppressWarnings("unchecked") List<ProductCatalog> productCatalogs = (List<ProductCatalog>) catalogManager.getCatalogs(true); int count = 0; if (productCatalogs != null) { for (ProductCatalog productCatalog : productCatalogs) { if (SimpleProductPage.isIncluded(productCatalog)) { ++count; } } } boolean showProductCatalogsItem = count > 1; installerMenu.findMenuItemByName(CATALOGS_MENU_ITEM_TEXT).setVisible(showProductCatalogsItem); installerMenu.layout(); } private void toggleMenu() { getInstallerMenu().setVisible(!getInstallerMenu().isVisible()); } public Installer getInstaller() { return installer; } public void setButtonsEnabled(boolean enabled) { menuButton.setEnabled(enabled); } private void enablePool(boolean poolEnabled) { if (this.poolEnabled != poolEnabled) { this.poolEnabled = poolEnabled; PREF_POOL_ENABLED.set(poolEnabled); } if (poolEnabled) { pool = P2Util.getAgentManager().getDefaultBundlePool(SetupUIPlugin.INSTANCE.getSymbolicName()); } else { pool = null; } bundlePoolSwitch.setSelected(poolEnabled); // FIXME: Enabled/Disabled state for bundle pooling? // if (poolButton != null) // { // poolButton.setImage(getBundlePoolImage()); // } } public BundlePool getPool() { return pool; } private SimpleInstallerMenu getInstallerMenu() { if (installerMenu == null) { installerMenu = createInstallerMenu(); } return installerMenu; } private SimpleInstallerMenu createInstallerMenu() { final SimpleInstallerMenu menu = new SimpleInstallerMenu(this); SimpleInstallerMenu.InstallerMenuItem updateInstallerItem = new SimpleInstallerMenu.InstallerMenuItem(menu); updateInstallerItem .setDefaultImage(SetupInstallerPlugin.INSTANCE.getSWTImage("simple/exclamation_circle.png")); updateInstallerItem .setHoverImage(SetupInstallerPlugin.INSTANCE.getSWTImage("simple/exclamation_circle_hover.png")); updateInstallerItem.setText(UPDATE_MENU_ITEM_TEXT); updateInstallerItem.setToolTipText("Install available updates"); updateInstallerItem.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { Runnable successRunnable = new Runnable() { public void run() { restart(); } }; ExceptionHandler<CoreException> exceptionHandler = new ExceptionHandler<CoreException>() { public void handleException(CoreException ex) { ErrorDialog.open(ex); } }; SelfUpdate.update(getShell(), updateResolution, successRunnable, exceptionHandler, null); } }); SimpleInstallerMenu.InstallerMenuItem catalogsItem = new SimpleInstallerMenu.InstallerMenuItem(menu); catalogsItem.setText(CATALOGS_MENU_ITEM_TEXT); catalogsItem.setToolTipText(ProductCatalogsDialog.DESCRIPTION); catalogsItem.setVisible(false); catalogsItem.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { ProductCatalogsDialog productCatalogsDialog = new ProductCatalogsDialog(SimpleInstallerDialog.this, catalogManager, true); if (productCatalogsDialog.open() == ProductCatalogsDialog.OK) { productPage.handleFilter(""); } } }); AccessUtil.setKey(catalogsItem, "catalogs"); // Label spacer1 = new Label(menu, SWT.NONE); // spacer1.setLayoutData(GridDataFactory.swtDefaults().hint(SWT.DEFAULT, 46).create()); SimpleInstallerMenu.InstallerMenuItem advancedModeItem = new SimpleInstallerMenu.InstallerMenuItem(menu); advancedModeItem.setText(ADVANCED_MENU_ITEM_TEXT); advancedModeItem.setToolTipText("Switch to advanced mode"); advancedModeItem.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { switchToAdvancedMode(); } }); SimpleInstallerMenu.InstallerMenuItemWithToggle bundlePoolsItem = new SimpleInstallerMenu.InstallerMenuItemWithToggle( menu); bundlePoolsItem.setText(BUNDLE_POOLS_MENU_ITEM_TEXT); bundlePoolsItem.setToolTipText(AgentManagerDialog.MESSAGE); // bundlePoolsItem.setDividerVisible(false); bundlePoolsItem.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { manageBundlePools(); } }); bundlePoolSwitch = bundlePoolsItem.getToggleSwitch(); bundlePoolSwitch.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { enablePool(bundlePoolSwitch.isSelected()); } }); SimpleInstallerMenu.InstallerMenuItem aboutItem = new SimpleInstallerMenu.InstallerMenuItem(menu); aboutItem.setText(ABOUT_MENU_ITEM_TEXT); aboutItem.setToolTipText("Show information about this installer"); aboutItem.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { showAbout(); } }); SimpleInstallerMenu.InstallerMenuItem exitItem = new SimpleInstallerMenu.InstallerMenuItem(menu); exitItem.setText(EXIT_MENU_ITEM_TEXT); exitItem.setDividerVisible(false); exitItem.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { exitSelected(); } }); return menu; } private void updateAvailable(boolean available) { menuButton.setNotificationVisible(available); installerMenu.findMenuItemByName(UPDATE_MENU_ITEM_TEXT).setVisible(available); installerMenu.layout(); } private void manageBundlePools() { final boolean[] enabled = { poolEnabled }; AgentManagerDialog dialog = new AgentManagerDialog(getShell()) { @Override protected void createUI(Composite parent) { final Button enabledButton = new Button(parent, SWT.CHECK); enabledButton.setText("Enable shared bundle pool"); enabledButton.setSelection(poolEnabled); enabledButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { enabled[0] = enabledButton.getSelection(); getComposite().setEnabled(enabled[0]); } }); new Label(parent, SWT.NONE); super.createUI(parent); getComposite().setEnabled(poolEnabled); } @Override protected void createButtonsForButtonBar(Composite parent) { super.createButtonsForButtonBar(parent); Button button = getButton(IDialogConstants.OK_ID); if (button != null) { button.setEnabled(false); } } @Override protected void elementChanged(Object element) { Button button = getButton(IDialogConstants.OK_ID); if (button != null) { button.setEnabled(element instanceof BundlePool); } } }; if (pool != null) { dialog.setSelectedElement(pool); } if (dialog.open() == AgentManagerDialog.OK) { enablePool(enabled[0]); pool = (BundlePool) dialog.getSelectedElement(); } } public boolean refreshJREs() { if (variablePage != null) { return variablePage.refreshJREs(); } return false; } public void showAbout() { String version = SelfUpdate.getProductVersion(); new AboutDialog(getShell(), version).open(); } public void productSelected(Product product) { variablePage.setProduct(product); switchToPage(variablePage); } public SimpleInstallerPage getTopPage() { return (SimpleInstallerPage) stackLayout.topControl; } private void doSwitch(final SimpleInstallerPage oldPage, final SimpleInstallerPage newPage) { if (oldPage != null) { oldPage.aboutToHide(); } stackLayout.topControl = newPage; stack.layout(); clearMessage(); newPage.aboutToShow(); newPage.setFocus(); } private void switchToPage(final SimpleInstallerPage newPage) { if (newPage != null) { SimpleInstallerPage oldPage = !pageStack.isEmpty() ? pageStack.peek() : null; if (oldPage == null || oldPage != newPage) { pageStack.push(newPage); doSwitch(oldPage, newPage); } } } public void switchToAdvancedMode() { setReturnCode(RETURN_ADVANCED); exitSelected(); } public void restart() { setReturnCode(RETURN_RESTART); exitSelected(); } protected void exitQuiet() { super.exitSelected(); } @Override protected void exitSelected() { for (SimpleInstallerPage page : pageStack) { if (page instanceof SimpleVariablePage) { SimpleVariablePage variablePage = (SimpleVariablePage) page; if (!variablePage.promptLaunchProduct("You're about to exit the installer")) { return; } break; } } exitQuiet(); } public void backSelected() { if (pageStack.size() <= 1) { return; } SimpleInstallerPage oldPage = pageStack.pop(); SimpleInstallerPage newPage = pageStack.peek(); doSwitch(oldPage, newPage); } public void showMessage(String message, SimpleMessageOverlay.Type type, boolean dismissAutomatically) { showMessage(message, type, dismissAutomatically, null); } public void showMessage(String message, SimpleMessageOverlay.Type type, boolean dismissAutomatically, Runnable action) { // Check if we can reuse the current message to reduce flickering. if (currentMessage == null || currentMessage.getType() != type || currentMessage.isDismissAutomatically() != dismissAutomatically || currentMessage.getAction() != action) { clearMessage(); currentMessage = new SimpleMessageOverlay(this, type, new ControlRelocator() { public void relocate(Control control) { Rectangle bounds = SimpleInstallerDialog.this.getBounds(); int x = bounds.x + 5; int y = bounds.y + 24; int width = bounds.width - 9; // Depending on the current page, the height varies. int height = pageStack.peek() instanceof SimpleProductPage ? 87 : 70; control.setBounds(x, y, width, height); } }, dismissAutomatically, action); } currentMessage.setMessage(message); currentMessage.setVisible(true); } public void clearMessage() { if (currentMessage != null) { if (!currentMessage.isDisposed()) { currentMessage.close(); } currentMessage = null; } } public void showReadme(URI readmeURI) { if (readmePage != null) { readmePage.setReadmeURI(readmeURI); switchToPage(readmePage); } else { OS.INSTANCE.openSystemBrowser(readmeURI.toString()); } } public void showInstallationLog(File installationLogFile) { installationLogPage.setInstallationLogFile(installationLogFile); switchToPage(installationLogPage); } public void showKeepInstaller() { switchToPage(keepInstallerPage); } @Override public void dispose() { // Ensure that no Browser widget has the focus. See bug 466902. menuButton.setFocus(); super.dispose(); } public static Font getFont(int relativeHeight, String style) { String height = relativeHeight == 0 ? "" : relativeHeight > 0 ? "+" + relativeHeight : Integer.toString(relativeHeight); return SetupInstallerPlugin.getFont(getDefaultFont(), org.eclipse.emf.common.util.URI.createURI("font:///" + height + "/" + style)); } static Font getDefaultFont() { if (defaultFont == null) { defaultFont = JFaceResources.getFont(SetupInstallerPlugin.FONT_LABEL_DEFAULT); if (defaultFont == null) { defaultFont = UIUtil.getDisplay().getSystemFont(); } } return defaultFont; } static Point getDefaultSize(Drawable drawable) { if (defaultSize == null) { defaultSize = computeSize(drawable, getDefaultFont(), 37, 38); } return defaultSize; } static Point computeSize(Drawable drawable, Font font, int x, int y) { GC gc = new GC(drawable); gc.setFont(font); try { int height = gc.getFontMetrics().getHeight(); int totalWidth = height * x; int totalHeight = height * y; return new Point(totalWidth, totalHeight); } finally { gc.dispose(); } } static String getCSS() { if (css == null) { try { css = readBundleResource("html/css/simpleInstaller.css"); } catch (IOException ex) { SetupInstallerPlugin.INSTANCE.log(ex); } } return css; } static String getPageTemplate() { if (pageTemplate == null) { try { pageTemplate = readBundleResource("html/PageTemplate.html"); // Embed CSS pageTemplate = pageTemplate.replace("%INSTALLER_CSS%", SimpleInstallerDialog.getCSS()); } catch (IOException ex) { SetupInstallerPlugin.INSTANCE.log(ex); } } return pageTemplate; } static String getProductTemplate() { if (productTemplate == null) { try { productTemplate = readBundleResource("html/ProductTemplate.html"); } catch (IOException ex) { SetupInstallerPlugin.INSTANCE.log(ex); } } return productTemplate; } static String getProductTemplateLarge() { if (productTemplateLarge == null) { try { productTemplateLarge = readBundleResource("html/ProductTemplateLarge.html"); } catch (IOException ex) { SetupInstallerPlugin.INSTANCE.log(ex); } } return productTemplateLarge; } private static String readBundleResource(final String name) throws IOException { try { BundleFile root = SetupInstallerPlugin.INSTANCE.getRootFile(); BundleFile child = root.getChild(name); return child.getContentsString(); } catch (Exception ex) { throw new IOExceptionWithCause(ex); } } /** * @author Eike Stepper */ private final class UpdateSearcher extends Thread { private Display display; public UpdateSearcher(Display display) { super("Simple Update Searcher"); this.display = display; } @Override public void run() { try { User user = getInstaller().getUser(); updateResolution = SelfUpdate.resolve(user, null); if (updateResolution != null && !display.isDisposed()) { display.asyncExec(new Runnable() { public void run() { updateAvailable(true); } }); } } catch (CoreException ex) { SetupInstallerPlugin.INSTANCE.log(ex); } } } }