/* ******************************************************************************
* 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.ArrayList;
import java.util.List;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.util.Util;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ControlListener;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.MouseTrackListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Layout;
import org.xmind.core.ISheet;
import org.xmind.core.ITopic;
import org.xmind.gef.IGraphicalViewer;
import org.xmind.gef.ui.actions.PageAction;
import org.xmind.gef.ui.editor.IGraphicalEditorPage;
import org.xmind.gef.ui.editor.IPanel;
import org.xmind.gef.ui.editor.PanelContribution;
import org.xmind.gef.ui.editor.PanelContributor;
import org.xmind.ui.internal.MindMapMessages;
import org.xmind.ui.mindmap.IDrillDownTraceListener;
import org.xmind.ui.mindmap.IDrillDownTraceService;
import org.xmind.ui.mindmap.IMindMap;
import org.xmind.ui.mindmap.MindMap;
import org.xmind.ui.mindmap.MindMapUI;
import org.xmind.ui.resources.ColorUtils;
import org.xmind.ui.resources.FontUtils;
import org.xmind.ui.util.MindMapUtils;
public class MindMapEditorPagePanelContributor extends PanelContributor {
protected static class CrumbItem implements IPropertyChangeListener {
private static final int H_MARGIN = 8;
private static final int V_MARGIN = 2;
private static final int M = 2;
private static final int M2 = M + 1;
private static final int W = M * 2 + 1;
private static final int W2 = M2 * 2;
private static final int C2 = W2 * 2 + 1;
private static final int C = C2 + 1;
private static final int SPACING = 1;
private CrumbsBar bar;
private IAction action;
private Rectangle bounds;
private boolean mouseOver;
private boolean pressed;
private Image image = null;
public CrumbItem() {
this(null);
}
public CrumbItem(IAction action) {
this.action = action;
}
void setParent(CrumbsBar bar) {
if (bar == this.bar)
return;
if (action != null) {
if (this.bar == null && bar != null) {
action.addPropertyChangeListener(this);
} else if (this.bar != null && bar == null) {
action.removePropertyChangeListener(this);
}
}
this.bar = bar;
}
public void setMouseOver(boolean mouseOver) {
if (mouseOver == this.mouseOver)
return;
this.mouseOver = mouseOver;
redraw();
}
public boolean isMouseOver() {
return mouseOver;
}
public void setPressed(boolean pressed) {
if (pressed == this.pressed)
return;
this.pressed = pressed;
redraw();
}
public boolean isPressed() {
return pressed;
}
public void setBounds(Rectangle bounds) {
if (bounds == this.bounds
|| (bounds != null && bounds.equals(this.bounds)))
return;
this.bounds = bounds;
redraw();
}
public Rectangle getBounds() {
if (bounds == null)
bounds = new Rectangle(0, 0, 0, 0);
return bounds;
}
public boolean isEnabled() {
return action != null && action.isEnabled();
}
private void redraw() {
if (barExists())
bar.redraw(this);
}
public boolean isSeparator() {
return action == null;
}
Point getPrefSize() {
if (!barExists())
return new Point(0, 0);
GC gc = new GC(Display.getCurrent());
gc.setFont(bar.getFont());
Point s = gc.textExtent(getText());
gc.dispose();
int h = getHMargin();
int v = getVMargin();
Image img = getImage();
if (img != null) {
Rectangle r = img.getBounds();
s.x += r.width + SPACING;
s.y = Math.max(s.y, r.height);
}
s.x += h + h + 1;
s.y += v + v + 1;
return s;
}
private int getHMargin() {
if (isSeparator())
return 0;
return H_MARGIN;
}
private int getVMargin() {
if (isSeparator())
return 0;
return V_MARGIN;
}
protected void paint(GC gc) {
gc.setAntialias(SWT.ON);
gc.setLineStyle(SWT.LINE_SOLID);
gc.setLineWidth(1);
if (pressed || mouseOver) {
if (pressed && mouseOver) {
gc.setBackground(ColorUtils.getColor("#a0a0a0")); //$NON-NLS-1$
} else if (pressed) {
gc.setBackground(ColorUtils.getColor("#707070")); //$NON-NLS-1$
} else {
gc.setBackground(ColorUtils.getColor("#e0e0e0")); //$NON-NLS-1$
}
gc.fillRoundRectangle(bounds.x + M2, bounds.y + M2,
bounds.width - W2, bounds.height - W2, C2, C2);
if (pressed && mouseOver) {
gc.setForeground(ColorUtils.getColor("#909090")); //$NON-NLS-1$
} else if (pressed) {
gc.setForeground(ColorUtils.getColor("#606060")); //$NON-NLS-1$
} else {
gc.setForeground(ColorUtils.getColor("#d0d0d0")); //$NON-NLS-1$
}
gc.drawRoundRectangle(bounds.x + M, bounds.y + M,
bounds.width - W, bounds.height - W, C, C);
}
int h = getHMargin();
int v = getVMargin();
int x = bounds.x + h;
int y = bounds.y + v;
int height = bounds.height - v - v;
Image img = getImage();
if (img != null) {
Rectangle r = img.getBounds();
gc.drawImage(img, x, y + (height - r.height) / 2);
x += r.width + SPACING;
}
if (isSeparator() || !isEnabled()) {
gc.setForeground(ColorUtils.getColor("#a0a0a0")); //$NON-NLS-1$
} else if (pressed) {
gc.setForeground(ColorUtils.getColor("#f0f0f0")); //$NON-NLS-1$
} else {
gc.setForeground(ColorUtils.getColor("#000000")); //$NON-NLS-1$
}
String text = getText();
Point s = gc.textExtent(text);
gc.drawText(text, x, y + (height - s.y) / 2, true);
}
protected String getText() {
if (action != null) {
String text = action.getText();
if (text != null) {
text = MindMapUtils.trimSingleLine(text);
return text;
}
return ""; //$NON-NLS-1$
}
return ">"; //$NON-NLS-1$
}
protected Image getImage() {
if (image == null && barExists()) {
if (action != null) {
ImageDescriptor imgDesc = null;
Display display = bar.getDisplay();
if (!isEnabled()) {
imgDesc = action.getDisabledImageDescriptor();
if (imgDesc != null) {
image = imgDesc.createImage(display);
}
if (image == null) {
imgDesc = action.getImageDescriptor();
if (imgDesc != null) {
Image img = imgDesc.createImage(display);
image = new Image(display, img,
SWT.IMAGE_DISABLE);
img.dispose();
}
}
} else {
imgDesc = action.getImageDescriptor();
if (imgDesc != null) {
image = imgDesc.createImage(display);
}
}
}
}
return image;
}
protected void run() {
if (action != null)
action.run();
}
public void update(String id) {
boolean textChange = id == null || IAction.TEXT.equals(id);
boolean tooltipChange = id == null
|| IAction.TOOL_TIP_TEXT.equals(id);
boolean enabledChange = id == null || IAction.ENABLED.equals(id);
boolean imageChange = id == null || IAction.IMAGE.equals(id);
boolean needUpdateBar = textChange || imageChange;
if (barExists()) {
bar.setRedraw(false);
}
if (enabledChange) {
redraw();
}
if (tooltipChange) {
if (action != null && barExists())
bar.setToolTipText(action.getToolTipText());
}
if (imageChange) {
if (image != null) {
image.dispose();
image = null;
}
}
if (needUpdateBar) {
if (barExists())
bar.updateLayout();
}
if (barExists()) {
bar.setRedraw(true);
}
}
public void propertyChange(PropertyChangeEvent event) {
update(event.getProperty());
}
public IAction getAction() {
return action;
}
protected boolean barExists() {
return bar != null && !bar.isDisposed();
}
protected void releaseResources() {
if (image != null) {
image.dispose();
image = null;
}
}
}
protected static class CrumbsBar extends Composite {
private static final int SPACING = 1;
private class CrumbsBarListener
implements PaintListener, MouseListener, MouseMoveListener,
MouseTrackListener, ControlListener, DisposeListener {
private CrumbItem sourceItem = null;
private CrumbItem targetItem = null;
public void paintControl(PaintEvent e) {
GC gc = e.gc;
if (!Util.isMac()) {
gc.setBackground(e.display
.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));
gc.fillRectangle(getBounds());
}
Rectangle clipping = gc.getClipping();
for (CrumbItem item : items) {
if (clipping.intersects(item.getBounds())) {
item.paint(gc);
}
}
}
public void mouseDoubleClick(MouseEvent e) {
}
public void mouseDown(MouseEvent e) {
CrumbItem item = findItem(e.x, e.y);
if (item != null && item.isEnabled() && !item.isSeparator()) {
sourceItem = item;
item.setPressed(true);
}
}
public void mouseUp(MouseEvent e) {
CrumbItem item = findItem(e.x, e.y);
CrumbItem source = sourceItem;
if (sourceItem != null) {
sourceItem.setPressed(false);
sourceItem = null;
}
if (item != null && item == source) {
item.run();
}
}
private void receiveTarget(int x, int y) {
CrumbItem item = findItem(x, y);
if (item != targetItem) {
if (targetItem != null && targetItem.isEnabled()
&& !targetItem.isSeparator()) {
targetItem.setMouseOver(false);
}
targetItem = item;
if (item != null && item.isEnabled()
&& !item.isSeparator()) {
if (sourceItem == null || item == sourceItem) {
item.setMouseOver(true);
}
}
String tooltip = null;
if (targetItem != null) {
IAction action = targetItem.getAction();
if (action != null) {
tooltip = action.getToolTipText();
}
}
setToolTipText(tooltip);
}
}
public void mouseMove(MouseEvent e) {
receiveTarget(e.x, e.y);
}
public void mouseEnter(MouseEvent e) {
receiveTarget(e.x, e.y);
}
public void mouseExit(MouseEvent e) {
receiveTarget(e.x, e.y);
}
public void mouseHover(MouseEvent e) {
receiveTarget(e.x, e.y);
}
public void controlMoved(ControlEvent e) {
updateLayout();
}
public void controlResized(ControlEvent e) {
updateLayout();
}
public void widgetDisposed(DisposeEvent e) {
releaseItems();
}
}
private List<CrumbItem> items = new ArrayList<CrumbItem>();
public CrumbsBar(Composite parent, int style) {
super(parent, style);
CrumbsBarListener eventHandler = new CrumbsBarListener();
addPaintListener(eventHandler);
addMouseListener(eventHandler);
addMouseMoveListener(eventHandler);
addMouseTrackListener(eventHandler);
addControlListener(eventHandler);
setFont(FontUtils.getRelativeHeight(JFaceResources.DEFAULT_FONT,
-1));
}
private void releaseItems() {
for (CrumbItem item : items) {
item.releaseResources();
}
}
protected void redraw(CrumbItem item) {
checkWidget();
Rectangle r = item.getBounds();
redraw(r.x, r.y, r.width, r.height, false);
}
public void addItem(CrumbItem item) {
checkWidget();
addItem(item, -1);
}
public void addItem(CrumbItem item, int index) {
checkWidget();
items.remove(item);
if (index < 0)
items.add(item);
else
items.add(index, item);
itemAdded(item);
}
public void removeItem(CrumbItem item) {
checkWidget();
items.remove(item);
itemRemoved(item);
}
private void itemAdded(CrumbItem item) {
item.setParent(this);
layout();
}
private void itemRemoved(CrumbItem item) {
item.setParent(null);
item.releaseResources();
layout();
}
public void removeAllItems() {
checkWidget();
setRedraw(false);
CrumbItem[] oldItems = getItems();
items.clear();
for (CrumbItem item : oldItems) {
itemRemoved(item);
}
layout();
setRedraw(true);
}
public CrumbItem[] getItems() {
checkWidget();
return items.toArray(new CrumbItem[items.size()]);
}
public CrumbItem findItem(Point p) {
checkWidget();
return findItem(p.x, p.y);
}
public CrumbItem findItem(int x, int y) {
checkWidget();
for (CrumbItem item : items) {
Rectangle r = item.getBounds();
if (r.contains(x, y))
return item;
}
return null;
}
public void setLayout(Layout layout) {
}
public void layout(boolean changed) {
updateLayout();
}
public void layout(boolean changed, boolean all) {
updateLayout();
}
protected void updateLayout() {
checkWidget();
Point p = new Point(0, 0);
int h = getSize().y;
for (CrumbItem item : items) {
Point s = item.getPrefSize();
Rectangle r = new Rectangle(p.x, p.y + (h - s.y) / 2, s.x, s.y);
item.setBounds(r);
p.x += s.x + SPACING;
}
redraw();
}
public Point computeSize(int wHint, int hHint, boolean changed) {
checkWidget();
if (wHint >= 0 && hHint >= 0)
return new Point(wHint, hHint);
Point size = new Point(0, 0);
if (wHint >= 0) {
size.x = wHint;
}
for (CrumbItem item : items) {
Point s = item.getPrefSize();
if (wHint < 0) {
if (size.x > 0)
size.x += SPACING;
size.x += s.x;
}
size.y = Math.max(size.y, s.y);
}
return size;
}
}
class Crumbs extends PanelContribution implements IDrillDownTraceListener {
private class QuickDrillUpAction extends PageAction {
private ITopic newCentralTopic;
public QuickDrillUpAction(IGraphicalEditorPage page, ITopic topic) {
super(page);
this.newCentralTopic = topic;
String title = topic.getTitleText();
setText(title);
setToolTipText(NLS.bind(
MindMapMessages.BreadCrumb_ViewAsCentral_text, title));
setImageDescriptor(MindMapUtils.getImageDescriptor(topic));
}
public void run() {
super.run();
IGraphicalViewer viewer = getViewer();
if (viewer != null) {
ISheet sheet = (ISheet) viewer.getAdapter(ISheet.class);
if (sheet != null) {
IMindMap newInput = new MindMap(sheet, newCentralTopic);
viewer.setInput(newInput);
if (viewer.getEditDomain() != null) {
viewer.getEditDomain().handleRequest(
MindMapUI.REQ_SELECT_CENTRAL, viewer);
}
}
}
}
}
private CrumbsBar bar = null;
private IDrillDownTraceService service = null;
public void createControl(Composite parent) {
if (!barExists()) {
bar = new CrumbsBar(parent, SWT.NONE);
}
}
public Control getControl() {
return bar;
}
public void update() {
boolean hasNewItems = service != null && service.canDrillUp();
if (barExists()) {
bar.removeAllItems();
bar.setRedraw(false);
if (hasNewItems) {
List<ITopic> topics = service.getCentralTopics();
if (!topics.isEmpty()) {
for (int i = 0; i < topics.size(); i++) {
ITopic t = topics.get(i);
QuickDrillUpAction action = new QuickDrillUpAction(
getPage(), t);
if (i == topics.size() - 1) {
action.setEnabled(false);
action.setImageDescriptor(null);
action.setToolTipText(NLS.bind(
MindMapMessages.BreadCrumb_CurrentCentral_text,
action.newCentralTopic.getTitleText()));
}
bar.addItem(new CrumbItem(action));
if (i < topics.size() - 1) {
bar.addItem(new CrumbItem());
}
}
}
}
bar.setRedraw(true);
setVisible(hasNewItems);
}
}
private boolean barExists() {
return bar != null && !bar.isDisposed();
}
public void traceChanged(IDrillDownTraceService traceService) {
update();
}
public void setTraceService(IDrillDownTraceService service) {
if (service == this.service)
return;
if (this.service != null) {
this.service.removeTraceListener(this);
}
this.service = service;
if (service != null) {
service.addTraceListener(this);
}
update();
}
}
private Crumbs crumbs = null;
protected void init(IPanel panel) {
super.init(panel);
crumbs = new Crumbs();
crumbs.setVisible(false);
panel.addContribution(IPanel.TOP, crumbs);
}
public void setViewer(IGraphicalViewer viewer) {
super.setViewer(viewer);
IDrillDownTraceService traceService = (IDrillDownTraceService) viewer
.getService(IDrillDownTraceService.class);
crumbs.setTraceService(traceService);
}
public void dispose() {
crumbs.setTraceService(null);
super.dispose();
}
}