/* ******************************************************************************
* Copyright (c) 2006-2012 XMind Ltd. and others.
*
* This file is a part of XMind 3. XMind releases 3 and
* above are dual-licensed under the Eclipse Public License (EPL),
* which is available at http://www.eclipse.org/legal/epl-v10.html
* and the GNU Lesser General Public License (LGPL),
* which is available at http://www.gnu.org/licenses/lgpl.html
* See http://www.xmind.net/license.html for details.
*
* Contributors:
* XMind Ltd. - initial API and implementation
*******************************************************************************/
package org.xmind.ui.internal.editor;
import java.util.List;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.jface.action.ContributionItem;
import org.eclipse.jface.dialogs.IPageChangedListener;
import org.eclipse.jface.dialogs.PageChangedEvent;
import org.eclipse.jface.dialogs.PopupDialog;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.util.SafeRunnable;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;
import org.xmind.gef.EditDomain;
import org.xmind.gef.GEF;
import org.xmind.gef.IGraphicalViewer;
import org.xmind.gef.IZoomListener;
import org.xmind.gef.Request;
import org.xmind.gef.ZoomManager;
import org.xmind.gef.ZoomObject;
import org.xmind.gef.ui.editor.IGraphicalEditor;
import org.xmind.gef.ui.editor.IGraphicalEditorPage;
import org.xmind.ui.internal.MindMapMessages;
import org.xmind.ui.internal.actions.ActionConstants;
import org.xmind.ui.mindmap.IMindMapImages;
import org.xmind.ui.mindmap.MindMapUI;
import org.xmind.ui.viewers.ISliderContentProvider;
import org.xmind.ui.viewers.SliderViewer;
public class MiniZoomContribution extends ContributionItem
implements IPageChangedListener, IZoomListener {
protected static final int SLIDER_WIDTH = 88;
private static class ZoomPopup extends PopupDialog {
private static final double[] directSelectorInput = { 2.0, 1.5, 1.2,
1.0, 0.8, 0.5 };
private static class ZoomSliderContentProvider
implements ISliderContentProvider {
private static final int intervalNum = directSelectorInput.length
- 1;
public double getRatio(Object input, Object value) {
if (value instanceof Double) {
double doubleValue = ((Double) value).doubleValue();
return transformValueToRatio(doubleValue);
}
return 0;
}
private double transformValueToRatio(double value) {
if (value > directSelectorInput[0])
return 1.0;
if (value < directSelectorInput[intervalNum])
return 0;
for (int index = intervalNum; index > 0; index--) {
if (isInternal(value, directSelectorInput[index],
directSelectorInput[index - 1])) {
double minRatio = (intervalNum - index) * 1.0
/ intervalNum;
double maxRatio = minRatio + 1.0 / intervalNum;
return calRatio(value, directSelectorInput[index],
directSelectorInput[index - 1], minRatio,
maxRatio);
}
}
return 0;
}
private boolean isInternal(double value, double minValue,
double maxValue) {
return value <= maxValue && value >= minValue;
}
private double calRatio(double value, double minValue,
double maxValue, double minRatio, double maxRatio) {
return minRatio + (value - minValue) / (maxValue - minValue)
* (maxRatio - minRatio);
}
private double calValue(double ratio, double minRatio,
double maxRatio, double minValue, double maxValue) {
return minValue + (ratio - minRatio) / (maxRatio - minRatio)
* (maxValue - minValue);
}
public Object getValue(Object input, double ratio) {
return transformRatioToValue(ratio);
}
private double transformRatioToValue(double ratio) {
if (ratio > 1)
return directSelectorInput[0] * ratio;
if (ratio < 0)
return 0;
for (int index = intervalNum; index > 0; index--) {
double minRatio = (intervalNum - index) * 1.0 / intervalNum;
double maxRatio = minRatio + 1.0 / intervalNum;
if (isInternal(ratio, minRatio, maxRatio)) {
return calValue(ratio, minRatio, maxRatio,
directSelectorInput[index],
directSelectorInput[index - 1]);
}
}
return 0.5;
}
public void inputChanged(Viewer viewer, Object oldInput,
Object newInput) {
}
public void dispose() {
}
}
private static class ZoomSliderLabelProvider extends LabelProvider {
public String getText(Object element) {
if (element instanceof Double) {
double scale = ((Double) element).doubleValue();
return String.format("%.0f%%", Double.valueOf(scale * 100)); //$NON-NLS-1$
}
return super.getText(element);
}
}
private Text valueInput;
private SliderViewer slider;
private IGraphicalEditorPage page;
private ZoomManager zoomManager;
private Point triggerPosition = null;
private boolean internalModifying = false;
private Listener directSelectorItemListener = new Listener() {
public void handleEvent(Event event) {
if (event.widget instanceof ToolItem) {
Double value = (Double) ((ToolItem) event.widget).getData();
confirmValue(value.doubleValue());
}
}
};
public ZoomPopup(Shell parent) {
super(parent, SWT.TOOL, true, false, false, false, false, null,
null);
}
public void update(IGraphicalEditorPage page, ZoomManager zoomManager,
Point pos) {
this.page = page;
this.zoomManager = zoomManager;
this.triggerPosition = pos;
}
protected Control createDialogArea(Composite parent) {
Composite composite = (Composite) super.createDialogArea(parent);
fillValueInput(composite);
Composite composite2 = new Composite(composite, SWT.NONE);
GridLayout layout2 = new GridLayout(2, false);
layout2.marginWidth = 0;
layout2.marginHeight = 0;
layout2.verticalSpacing = 0;
layout2.horizontalSpacing = 0;
composite2.setLayout(layout2);
composite2.setLayoutData(
new GridData(SWT.FILL, SWT.FILL, true, true));
fillDirectSelector(composite2);
fillSlider(composite2);
return composite;
}
private void fillValueInput(Composite parent) {
valueInput = new Text(parent, SWT.BORDER | SWT.SINGLE);
valueInput.setLayoutData(
new GridData(SWT.FILL, SWT.FILL, true, false));
valueInput.addListener(SWT.Modify, new Listener() {
public void handleEvent(Event event) {
if (internalModifying)
return;
internalModifying = true;
try {
int intValue = Integer.parseInt(valueInput.getText(),
10);
updateSlider(intValue / 100.0);
} catch (NumberFormatException e) {
// ignore
} finally {
internalModifying = false;
}
}
});
Listener inputConfirmListener = new Listener() {
public void handleEvent(Event event) {
if (event.type == SWT.DefaultSelection
|| (event.type == SWT.Traverse
&& event.detail == SWT.TRAVERSE_RETURN)) {
try {
int intValue = Integer
.parseInt(valueInput.getText(), 10);
confirmValue(intValue / 100.0);
} catch (NumberFormatException e) {
// ignore
}
event.doit = false;
}
}
};
valueInput.addListener(SWT.DefaultSelection, inputConfirmListener);
valueInput.addListener(SWT.Traverse, inputConfirmListener);
}
private void fillDirectSelector(Composite parent) {
ToolBar toolbar = new ToolBar(parent, SWT.VERTICAL);
toolbar.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
addDirectSelectorItems(toolbar, directSelectorInput);
}
private void addDirectSelectorItems(ToolBar parent, double[] values) {
for (double value : values) {
addDirectSelectorItem(parent, value);
}
}
private void addDirectSelectorItem(ToolBar parent, double value) {
ToolItem item = new ToolItem(parent, SWT.PUSH);
item.setText(String.valueOf((int) Math.round(value * 100)));
item.setData(Double.valueOf(value));
item.addListener(SWT.Selection, directSelectorItemListener);
}
private void fillSlider(Composite parent) {
slider = new SliderViewer(parent, SWT.VERTICAL);
slider.getControl().setLayoutData(
new GridData(SWT.FILL, SWT.FILL, false, true));
((GridData) slider.getControl().getLayoutData()).heightHint = 40;
slider.setContentProvider(new ZoomSliderContentProvider());
slider.setLabelProvider(new ZoomSliderLabelProvider());
slider.setInput(this);
slider.addSelectionChangedListener(new ISelectionChangedListener() {
public void selectionChanged(SelectionChangedEvent event) {
if (internalModifying)
return;
internalModifying = true;
try {
Double value = (Double) slider.getSelectionValue();
updateValueInput(value.doubleValue());
} catch (NumberFormatException e) {
// ignore
} finally {
internalModifying = false;
}
}
});
slider.addPostSelectionChangedListener(
new ISelectionChangedListener() {
public void selectionChanged(
SelectionChangedEvent event) {
if (internalModifying)
return;
Double value = (Double) slider.getSelectionValue();
if (value != null) {
confirmValue(value.doubleValue());
}
}
});
}
protected Control getFocusControl() {
return valueInput;
}
protected void adjustBounds() {
internalModifying = true;
try {
double doubleValue = zoomManager.getScale();
updateValueInput(doubleValue);
updateSlider(doubleValue);
} finally {
internalModifying = false;
}
getShell().pack();
Point size = getShell().getSize();
if (triggerPosition != null) {
getShell().setLocation(triggerPosition.x,
triggerPosition.y - size.y);
}
}
private void confirmValue(final double newValue) {
close();
SafeRunner.run(new SafeRunnable() {
public void run() throws Exception {
if (page == null)
return;
EditDomain domain = page.getEditDomain();
if (domain == null)
return;
Request request = new Request(GEF.REQ_ZOOM);
request.setViewer(page.getViewer());
request.setParameter(GEF.PARAM_ZOOM_SCALE,
Double.valueOf(newValue));
domain.handleRequest(request);
}
});
}
private void updateValueInput(double doubleValue) {
valueInput.setText(
String.valueOf((int) Math.round(doubleValue * 100)));
valueInput.setSelection(valueInput.getText().length());
}
private void updateSlider(double doubleValue) {
slider.setSelection(
new StructuredSelection(Double.valueOf(doubleValue)));
}
@SuppressWarnings("unchecked")
protected List getBackgroundColorExclusions() {
List list = super.getBackgroundColorExclusions();
list.add(valueInput);
return list;
}
}
private ZoomManager zoomManager = null;
private IGraphicalEditor editor;
private ToolItem zoomOutItem;
private ToolItem zoomValueItem;
private ToolItem zoomInItem;
private ToolItem zoomDefaultItem;
private ZoomPopup zoomPopup = null;
public MiniZoomContribution(IGraphicalEditor editor) {
this(ActionConstants.MINI_ZOOM, editor);
}
public MiniZoomContribution(String id, IGraphicalEditor editor) {
super(id);
this.editor = editor;
editor.addPageChangedListener(this);
}
public boolean isDynamic() {
return true;
}
public void dispose() {
if (zoomOutItem != null) {
zoomOutItem.dispose();
zoomOutItem = null;
}
if (zoomValueItem != null) {
zoomValueItem.dispose();
zoomValueItem = null;
}
if (zoomInItem != null) {
zoomInItem.dispose();
zoomInItem = null;
}
if (zoomDefaultItem != null) {
zoomDefaultItem.dispose();
zoomDefaultItem = null;
}
setZoomManager(null);
if (editor != null) {
editor.removePageChangedListener(this);
editor = null;
}
super.dispose();
}
public void fill(ToolBar parent, int index) {
if (index < 0) {
zoomOutItem = new ToolItem(parent, SWT.PUSH);
zoomValueItem = new ToolItem(parent, SWT.PUSH);
zoomInItem = new ToolItem(parent, SWT.PUSH);
zoomDefaultItem = new ToolItem(parent, SWT.PUSH);
} else {
zoomOutItem = new ToolItem(parent, SWT.PUSH, index++);
zoomValueItem = new ToolItem(parent, SWT.PUSH, index++);
zoomInItem = new ToolItem(parent, SWT.PUSH, index++);
zoomDefaultItem = new ToolItem(parent, SWT.PUSH, index++);
}
zoomValueItem.addListener(SWT.Selection, new Listener() {
public void handleEvent(Event event) {
Rectangle r = zoomValueItem.getBounds();
showZoomPopup(event,
zoomValueItem.getParent().toDisplay(r.x, r.y));
}
});
zoomOutItem.addListener(SWT.Selection, new Listener() {
public void handleEvent(Event event) {
sendRequest(GEF.REQ_ZOOMOUT);
}
});
zoomInItem.addListener(SWT.Selection, new Listener() {
public void handleEvent(Event event) {
sendRequest(GEF.REQ_ZOOMIN);
}
});
zoomDefaultItem.addListener(SWT.Selection, new Listener() {
public void handleEvent(Event event) {
sendRequest(GEF.REQ_ACTUALSIZE);
}
});
initItemLabel(zoomOutItem, IMindMapImages.ZOOMOUT_SMALL,
MindMapMessages.ZoomOut_toolTip);
initItemLabel(zoomInItem, IMindMapImages.ZOOMIN_SMALL,
MindMapMessages.ZoomIn_toolTip);
initItemLabel(zoomDefaultItem, IMindMapImages.ACTUAL_SIZE_SMALL,
MindMapMessages.ActualSize_toolTip);
update(null);
}
private void initItemLabel(final ToolItem item, String iconPath,
String fallbackText) {
Image image = createImage(iconPath, true);
if (image != null) {
item.setImage(image);
item.setDisabledImage(createImage(iconPath, false));
item.addDisposeListener(new DisposeListener() {
public void widgetDisposed(DisposeEvent e) {
Image image = item.getImage();
if (image != null) {
image.dispose();
}
image = item.getDisabledImage();
if (image != null) {
image.dispose();
}
}
});
} else {
item.setText(fallbackText);
}
}
private Image createImage(String iconPath, boolean enabled) {
ImageDescriptor imageDescriptor = MindMapUI.getImages().get(iconPath,
enabled);
return imageDescriptor == null ? null
: imageDescriptor.createImage(false);
}
public void update() {
update(null);
}
public void update(String id) {
if (zoomValueItem != null && !zoomValueItem.isDisposed()) {
if (zoomManager == null) {
zoomValueItem.setText("100%"); //$NON-NLS-1$
} else {
double scale = zoomManager.getScale();
int s = (int) Math.round(scale * 100);
zoomValueItem.setText(String.format("%d%%", s)); //$NON-NLS-1$
}
}
}
private void refresh() {
update(null);
getParent().update(true);
}
public void pageChanged(PageChangedEvent event) {
setZoomManager(getZoomManager(event.getSelectedPage()));
}
public void scaleChanged(ZoomObject source, double oldValue,
double newValue) {
refresh();
}
private ZoomManager getZoomManager(Object page) {
if (page == null || !(page instanceof IGraphicalEditorPage))
return null;
IGraphicalViewer viewer = ((IGraphicalEditorPage) page).getViewer();
if (viewer == null)
return null;
return viewer.getZoomManager();
}
private void setZoomManager(ZoomManager zoomManager) {
if (zoomManager == this.zoomManager)
return;
if (this.zoomManager != null) {
this.zoomManager.removeZoomListener(this);
}
this.zoomManager = zoomManager;
if (zoomManager != null) {
zoomManager.addZoomListener(this);
}
refresh();
}
private void showZoomPopup(Event event, Point itemLocation) {
if (zoomManager == null)
return;
if (zoomPopup == null) {
zoomPopup = new ZoomPopup(editor.getSite().getShell());
}
zoomPopup.update(editor.getActivePageInstance(), zoomManager,
itemLocation);
zoomPopup.open();
}
private void sendRequest(final String reqType) {
if (editor == null)
return;
final IGraphicalEditorPage page = editor.getActivePageInstance();
if (page == null)
return;
final EditDomain domain = page.getEditDomain();
if (domain == null)
return;
SafeRunner.run(new SafeRunnable() {
public void run() throws Exception {
domain.handleRequest(reqType, page.getViewer());
}
});
}
}