/******************************************************************************* * Copyright (c) 2007, 2014 compeople AG 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: * compeople AG - initial API and implementation *******************************************************************************/ package org.eclipse.riena.ui.swt; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.jface.window.ApplicationWindow; 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.MouseTrackAdapter; import org.eclipse.swt.events.MouseTrackListener; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.layout.FormAttachment; import org.eclipse.swt.layout.FormData; import org.eclipse.swt.layout.FormLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.ProgressBar; import org.eclipse.swt.widgets.Shell; import org.eclipse.riena.ui.core.uiprocess.UIProcess; import org.eclipse.riena.ui.swt.facades.SWTFacade; import org.eclipse.riena.ui.swt.lnf.LnfKeyConstants; import org.eclipse.riena.ui.swt.lnf.LnfManager; import org.eclipse.riena.ui.swt.nls.Messages; import org.eclipse.riena.ui.swt.uiprocess.ProcessState; import org.eclipse.riena.ui.swt.uiprocess.ProgressInfoDataObject; import org.eclipse.riena.ui.swt.utils.SwtUtilities; /** * The {@link StatuslineUIProcess} shows info about all currently running {@link UIProcess}�s in the {@link Statusline} */ public class StatuslineUIProcess extends AbstractStatuslineComposite { /** * the minimum value for the progress */ public static final int PROGRESS_MIN_VALUE = 0; /** * the maximum value for the progress */ public static final int PROGRESS_MAX_VALUE = 100; private final static int PORGRESS_BAR_HEIGHT = 14; // the base progress bar rendered inside the status line private ProgressBar progressBar; // the window containing the info table private PopupList popup; private Composite popupContent; private List<ProgressInfoDataObject> pidos = new ArrayList<ProgressInfoDataObject>(); private final Map<ProcessState, ILabelFormatter> stateValueMappers = new HashMap<ProcessState, ILabelFormatter>(); // maps the pido�s key to a ControlHolder private final Map<Integer, ControlHolder> pido2controlHolder = new HashMap<Integer, ControlHolder>(); // cache for data objects private final Map<Integer, ProgressInfoDataObject> valueCache = new HashMap<Integer, ProgressInfoDataObject>(); private Label statusLabel; private Label openLabel; private Label noProcessActiveLabel; /** * @param parent * the parent composite * @param style */ public StatuslineUIProcess(final Composite parent, final int style) { super(parent, style); initStateMappers(); observeMoveAndResize(parent); } private void observeMoveAndResize(final Composite parent) { //observe shell movement and resize final ControlAdapter listener = new ControlAdapter() { @Override public void controlMoved(final ControlEvent e) { configureShell(); } @Override public void controlResized(final ControlEvent e) { configureShell(); } }; addControlListener(listener); parent.getShell().addControlListener(listener); final Control grabCorner = locateGrabCorner(); if (grabCorner != null) { final MouseTrackListener trackListener = new MouseTrackAdapter() { @Override public void mouseExit(final MouseEvent e) { super.mouseExit(e); configureShell(); } }; SWTFacade.getDefault().addMouseTrackListener(grabCorner, trackListener); } } private void configureShell() { if (popup.getShell() != null && popup.getShell().isVisible()) { placeShell(); } } private Control locateGrabCorner() { final Shell shell = getShell(); for (final Control child : shell.getChildren()) { if ("grabcorner".equals(child.getData("sizeexecutor"))) { //$NON-NLS-1$ //$NON-NLS-2$ return child; } } // no recursion return null; } /** * init mappers for label formatting */ private void initStateMappers() { stateValueMappers.put(ProcessState.PENDING, new ILabelFormatter() { public String formatValue(final int value) { return "..."; //$NON-NLS-1$ } }); stateValueMappers.put(ProcessState.RUNNING, new ILabelFormatter() { public String formatValue(final int value) { return value + " %"; //$NON-NLS-1$ } }); stateValueMappers.put(ProcessState.FINISHED, new ILabelFormatter() { public String formatValue(final int value) { return Messages.StatuslineUIProcess_finished; } }); stateValueMappers.put(ProcessState.CANCELED, new ILabelFormatter() { public String formatValue(final int value) { return Messages.StatuslineUIProcess_canceled; } }); } /** * @return the progressBar */ public ProgressBar getProgressBar() { return progressBar; } /** * <pre> * * The content should look nearly like this: * * * +------------------------------------+ * + UIProcess Label |[][][][] | + * +------------------------------------+ * </pre> */ @Override protected void createContents() { initLayout(); statusLabel = new Label(this, SWT.NONE); statusLabel.setBackground(LnfManager.getLnf().getColor(LnfKeyConstants.STATUSLINE_BACKGROUND)); openLabel = new Label(this, SWT.NONE); openLabel.setImage(LnfManager.getLnf().getImage(LnfKeyConstants.STATUSLINE_UI_PROCESS_ICON)); openLabel.addMouseListener(new PopupController()); // openLabel.setText("open"); //$NON-NLS-1$ TODO openLabel.setBackground(LnfManager.getLnf().getColor(LnfKeyConstants.STATUSLINE_BACKGROUND)); FormData formData = new FormData(); formData.top = new FormAttachment(18, 0); formData.width = SwtUtilities.convertXToDpi(60); formData.height = SwtUtilities.convertYToDpi(PORGRESS_BAR_HEIGHT); progressBar = new ProgressBar(this, SWT.HORIZONTAL); progressBar.setLayoutData(formData); progressBar.setBackground(LnfManager.getLnf().getColor(LnfKeyConstants.STATUSLINE_BACKGROUND)); formData.left = new FormAttachment(statusLabel, SwtUtilities.convertXToDpi(5)); // minimum value is always 0 progressBar.setMinimum(PROGRESS_MIN_VALUE); // maximum is always 100 progressBar.setMaximum(PROGRESS_MAX_VALUE); progressBar.setSelection(0); formData = new FormData(); formData.top = new FormAttachment(18, 0); // formData.height = 14; formData.bottom = new FormAttachment(100, 0); formData.width = SwtUtilities.convertXToDpi(100); statusLabel.setLayoutData(formData); formData = new FormData(); formData.top = new FormAttachment(18, 0); formData.left = new FormAttachment(progressBar, SwtUtilities.convertXToDpi(5)); formData.bottom = new FormAttachment(100, 0); // formData.height = 14; openLabel.setLayoutData(formData); // create the popup popup = new PopupList(getShell()); } /** * observes the open/close {@link Label} */ class PopupController extends MouseAdapter { @Override public void mouseDown(final MouseEvent e) { if (popup.getShell() == null) { openPopup(); return; } closePopup(); } } private void openPopup() { popup.setBlockOnOpen(false); popup.open(); popup.getShell().setVisible(false); triggerListUpdate(pidos, true); } private void closePopup() { popup.close(); // clear the caches pido2controlHolder.clear(); valueCache.clear(); } /** * the {@link ApplicationWindow} for the list of processes */ private class PopupList extends ApplicationWindow { public PopupList(final Shell parentShell) { super(parentShell); } @Override protected Control createContents(final Composite parent) { parent.setBackground(LnfManager.getLnf().getColor(LnfKeyConstants.STATUSLINE_UI_PROCESS_LIST_BACKGROUND)); popupContent = parent; final FormLayout layout = new FormLayout(); layout.marginLeft = SwtUtilities.convertXToDpi(5); layout.marginRight = SwtUtilities.convertXToDpi(5); layout.marginTop = SwtUtilities.convertYToDpi(5); layout.marginBottom = SwtUtilities.convertYToDpi(5); parent.setLayout(layout); return parent; } @Override protected void configureShell(final Shell shell) { super.configureShell(shell); final Rectangle rect = StatuslineUIProcess.this.getBounds(); final Point display = toDisplay(new Point(rect.x, rect.y)); final int h = SwtUtilities.convertYToDpi(100); final int w = SwtUtilities.convertXToDpi(400); shell.setBounds(rect.x, display.y + h, h, w); } @Override protected int getShellStyle() { return SWT.NO_TRIM | SWT.NO_FOCUS | SWT.BORDER; } @Override protected boolean showTopSeperator() { return false; } } private void initLayout() { final FormLayout layout = new FormLayout(); layout.marginRight = SwtUtilities.convertXToDpi(5); setLayout(layout); } /** * updates uiprocess visualization directly in the StatusLine */ public void triggerBaseUpdate(final ProgressInfoDataObject pido) { if (pidoComplete(pido)) { resetStatusLineProgressBar(); return; } updateProgressBar(pido, getProgressBar()); updateLabel(pido, statusLabel); } private boolean pidoComplete(final ProgressInfoDataObject pido) { return (pido.getValue() >= 100 && !pido.getProcessState().equals(ProcessState.PENDING)) || ProcessState.CANCELED.equals(pido.getProcessState()) || ProcessState.FINISHED.equals(pido.getProcessState()); } private void resetStatusLineProgressBar() { if (!getProgressBar().isDisposed()) { getProgressBar().setSelection(0); statusLabel.setText(""); //$NON-NLS-1$ } } /** * updates the given {@link ProgressBar} with the value of the given {@link ProgressInfoDataObject} * * @param pido * @param progressBar */ private static void updateProgressBar(final ProgressInfoDataObject pido, final ProgressBar progressBar) { if (!progressBar.isDisposed()) { progressBar.setMaximum(100); progressBar.setSelection(pido.getValue()); } } /** * updates the list of visualized {@link UIProcess}es * * @param pidos * the progress info data objects */ public void triggerListUpdate(final List<ProgressInfoDataObject> pidos) { triggerListUpdate(pidos, false); } /** * updates the list of visualized {@link UIProcess}es * * @param pidos * the progress info data objects * @param forceListUpdate * determines if a list update should be forced even when there is no new data */ public void triggerListUpdate(final List<ProgressInfoDataObject> pidos, final boolean forceListUpdate) { if (complete(pidos)) { resetStatusLineProgressBar(); } if (!isDisposed() && isVisible()) { if (popVisible() && (forceListUpdate || !pidos.equals(this.pidos))) { // rebuild table createProcessList(pidos); } // save pidos this.pidos = new ArrayList<ProgressInfoDataObject>(); this.pidos.addAll(pidos); if (popVisible()) { // update progressbars placeShell(); updateListProgressBars(); popup.getShell().setVisible(true); } } else { this.pidos = new ArrayList<ProgressInfoDataObject>(); this.pidos.addAll(pidos); } } private boolean complete(final List<ProgressInfoDataObject> pidos) { if (pidos.size() == 0) { return true; } for (final ProgressInfoDataObject pido : pidos) { if (!pidoComplete(pido)) { return false; } } return true; } private boolean popVisible() { final Shell shell = popup.getShell(); return shell != null && !shell.isDisposed(); } /** * TODO call every time the client is moved or resized */ private void placeShell() { // pack it popup.getShell().pack(); final Point point = toDisplay(0, 0); final Rectangle popupBounds = popup.getShell().getBounds(); final int x = point.x + getBounds().width - popupBounds.width; final int y = point.y - popupBounds.height - 4; popup.getShell().setBounds(x, y, popupBounds.width, popupBounds.height); } private void updateListProgressBars() { // update ProgressBars for (final ProgressInfoDataObject pido : pidos) { if (freshValue(pido)) { //update existing bar final ControlHolder holder = pido2controlHolder.get(pido.getKey()); if (holder != null) { updateProgressBar(pido, holder.progressBar); updateLabel(pido, holder.label); } cacheValue(pido); } } } /** * updates the given {@link Label} with the value of the given {@link ProgressInfoDataObject}. An {@link ILabelFormatter}-strategy formats the value. * * @param pido * @param label */ private void updateLabel(final ProgressInfoDataObject pido, final Label label) { if ((label != null) && (!label.isDisposed())) { final String strV = pido.getProcessName() + " " //$NON-NLS-1$ + stateValueMappers.get(pido.getProcessState()).formatValue(pido.getValue()); label.setText(strV); } } /** * strategy for formatting values of a {@link Label} */ interface ILabelFormatter { /** * takes the value as {@link UIProcess} procentual progress */ String formatValue(int value); } /** * caches a {@link ProgressInfoDataObject}. * * @param pido */ private void cacheValue(final ProgressInfoDataObject pido) { valueCache.put(pido.getKey(), pido); } /** * determines if the {@link ProgressInfoDataObject} has changed * * @param pido * @return true if the {@link ProgressInfoDataObject} has changed */ private boolean freshValue(final ProgressInfoDataObject pido) { final ProgressInfoDataObject cached = valueCache.get(pido.getKey()); return cached == null || cached.compareTo(pido) != 0; } /** * just a container for a {@link ProgressBar} and a {@link Label} for caching */ private final static class ControlHolder { private ProgressBar progressBar; private Label label; private ControlHolder() { // private constructor of utility class. } } /** * creates a list of {@link UIProcess} infos in the popup */ private void createProcessList(final List<ProgressInfoDataObject> pidos) { checkInActiveProcesses(pidos); Control lastControl = null; ProgressBar bar; Label label; final Set<Integer> keys = new HashSet<Integer>(); for (final ProgressInfoDataObject pido : pidos) { if (!pidoComplete(pido)) { keys.add(pido.getKey()); if (noProcessActiveLabel != null) { if (!noProcessActiveLabel.isDisposed()) { noProcessActiveLabel.setVisible(false); noProcessActiveLabel.dispose(); } popupContent.layout(true); } ControlHolder holder = pido2controlHolder.get(pido.getKey()); if (holder == null) { holder = createControlHolder(pido); } bar = holder.progressBar; label = holder.label; //do layout stuff final Integer progressBarWidth = SwtUtilities.convertXToDpi(60); FormData formData = new FormData(); formData.top = lastControl != null ? new FormAttachment(lastControl, 2) : new FormAttachment(5, 0); formData.bottom = new FormAttachment(100, 0); // formData.height = SwtUtilities.convertYToDpi(14); final Integer lnfWidth = LnfManager.getLnf().getIntegerSetting(LnfKeyConstants.STATUSLINE_UI_PROCESS_LIST_WIDTH, 160); formData.width = SwtUtilities.convertXToDpi(lnfWidth) - progressBarWidth; label.setLayoutData(formData); formData = new FormData(); formData.top = lastControl != null ? new FormAttachment(lastControl, SwtUtilities.convertYToDpi(2)) : new FormAttachment(5, 0); formData.left = new FormAttachment(label, SwtUtilities.convertXToDpi(3)); // formData.bottom = new FormAttachment(100, 0); formData.height = SwtUtilities.convertYToDpi(14); formData.width = progressBarWidth; bar.setLayoutData(formData); lastControl = bar; } } // dispose and clear all controls of zombie pidos clearZombies(keys); popupContent.layout(); popupContent.redraw(); } private void checkInActiveProcesses(final List<ProgressInfoDataObject> pidos) { if (complete(pidos)) { cleanInactiveProcessPresentation(); } else { if (noProcessActiveLabel != null) { noProcessActiveLabel.dispose(); } } } private void cleanInactiveProcessPresentation() { final FormData formData = new FormData(); formData.top = new FormAttachment(5, 0); formData.bottom = new FormAttachment(100, 0); // formData.height = 14; final Integer lnfWidth = LnfManager.getLnf().getIntegerSetting(LnfKeyConstants.STATUSLINE_UI_PROCESS_LIST_WIDTH, 160); formData.width = SwtUtilities.convertXToDpi(lnfWidth); if (noProcessActiveLabel == null || noProcessActiveLabel.isDisposed()) { noProcessActiveLabel = new Label(popupContent, SWT.NONE); noProcessActiveLabel.setBackground(LnfManager.getLnf().getColor(LnfKeyConstants.STATUSLINE_UI_PROCESS_LIST_BACKGROUND)); noProcessActiveLabel.setText(Messages.StatuslineUIProcess_noActiveProcess); noProcessActiveLabel.setLayoutData(formData); } } /** * Creates a basic {@link ControlHolder} with a {@link ProgressBar} and a {@link Label} * * @param pido * @return the {@link ControlHolder} */ private ControlHolder createControlHolder(final ProgressInfoDataObject pido) { ControlHolder holder; holder = new ControlHolder(); holder.progressBar = new ProgressBar(popupContent, SWT.NONE); holder.progressBar.setBackground(LnfManager.getLnf().getColor(LnfKeyConstants.STATUSLINE_BACKGROUND)); holder.progressBar.setMaximum(100); holder.label = new Label(popupContent, SWT.NONE); holder.label.setBackground(LnfManager.getLnf().getColor(LnfKeyConstants.STATUSLINE_UI_PROCESS_LIST_BACKGROUND)); pido2controlHolder.put(pido.getKey(), holder); return holder; } /** * clears all {@link ControlHolder} related to {@link ProgressInfoDataObject}-keys which are no more needed * * @param activeKeys * the {@link Set} of keys defining the amount of active {@link ProgressInfoDataObject} instances */ private void clearZombies(final Set<Integer> activeKeys) { //clear all that are no more needed final Iterator<Integer> registeredKeys = pido2controlHolder.keySet().iterator(); while (registeredKeys.hasNext()) { final Integer key = registeredKeys.next(); if (!activeKeys.contains(key)) { // dispose the ProgressBar final ControlHolder holder = pido2controlHolder.get(key); holder.progressBar.dispose(); holder.label.dispose(); // remove entry registeredKeys.remove(); // clean value cache valueCache.remove(key); } } popup.getShell().redraw(); } }