/* ******************************************************************************
* 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.properties;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jface.resource.ColorDescriptor;
import org.eclipse.jface.resource.FontDescriptor;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
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.graphics.TextLayout;
import org.eclipse.swt.graphics.TextStyle;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Layout;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.xmind.ui.util.IChained;
import org.xmind.ui.viewers.ILabelDescriptor;
public class PropertyEditingEntry
implements IPropertyEditingEntry, IChained<PropertyEditingEntry> {
private static final int MARGIN_V = 5;
private static final int MARGIN_H = 8;
private static final int CORNER = 8;
private static final int HOVER_CORNER = 6;
private static final int HOVER_SHRINK_V = 4;
private static final int HOVER_SHRINK_H = 3;
// private static final float RADIUS = 8;
// private static final float RADIUS_BEND = RADIUS / 4;
protected class PropertyEditingCanvas extends Canvas {
/**
* @param parent
* @param style
*/
public PropertyEditingCanvas(Composite parent, int style) {
super(parent, style);
}
public void copy() {
handleCopy();
}
public void paste() {
handlePaste();
}
}
private PropertiesEditor parent;
private IPropertySource source;
private IPropertyDescriptor descriptor;
private Canvas canvas;
private TextLayout nameLayout;
private TextLayout valueLayout;
private Image valueImage = null;
private Color valueColor = null;
private Font valueFont = null;
private PropertyEditor editor;
private Color foreground = null;
private Font font = null;
private Color selectedBackground = null;
private Color selectedForeground = null;
private Font selectedFont = null;
private boolean selected = false;
private boolean showingHover = false;
private boolean showingEditorHover = false;
private PropertyEditingEntry prev = null;
private PropertyEditingEntry next = null;
public PropertyEditingEntry(PropertiesEditor parent, IPropertySource source,
IPropertyDescriptor descriptor) {
this.parent = parent;
this.source = source;
this.descriptor = descriptor;
}
@SuppressWarnings("unchecked")
public Object getAdapter(Class adapter) {
if (adapter == IPropertySource.class)
return source;
if (adapter == IPropertyDescriptor.class)
return descriptor;
return null;
}
public boolean isEditable() {
return editor != null;
}
public boolean isResettable() {
return source.isPropertyResettable(descriptor.getId());
}
/*
* (non-Javadoc)
* @see org.xmind.ui.properties.IPropertyEditingEntry#isPropertySet()
*/
public boolean isPropertySet() {
return source.isPropertySet(descriptor.getId());
}
public void resetPropertyValue() {
source.resetPropertyValue(descriptor.getId());
update();
}
public void createControl(Composite parent) {
Assert.isTrue(canvas == null);
nameLayout = new TextLayout(parent.getDisplay());
nameLayout.setAlignment(SWT.LEFT);
valueLayout = new TextLayout(parent.getDisplay());
valueLayout.setAlignment(SWT.RIGHT);
canvas = new PropertyEditingCanvas(parent, SWT.NONE);
canvas.setToolTipText(descriptor.getDescription());
canvas.setLayout(new Layout() {
protected void layout(Composite composite, boolean flushCache) {
if (flushCache) {
clearLayoutCache();
}
layoutControls();
}
protected Point computeSize(Composite composite, int wHint,
int hHint, boolean flushCache) {
if (flushCache) {
clearLayoutCache();
}
return computeCanvasSize(wHint, hHint);
}
});
Listener listener = new Listener() {
long lastTime = 0;
boolean pressedInEditorHover = false;
public void handleEvent(Event event) {
switch (event.type) {
case SWT.Paint:
paintCanvas(event.gc, event.x, event.y, event.width,
event.height);
break;
case SWT.FocusIn:
selectSingle();
break;
case SWT.FocusOut:
// asyncCheckFocus();
break;
case SWT.Traverse:
event.doit = handleKeyTraverse(event.detail);
break;
case SWT.KeyDown:
handleKeyPress(event.keyCode, event.stateMask);
break;
case SWT.MouseDown:
long currentTime = System.currentTimeMillis();
if (currentTime - lastTime > Display.getCurrent()
.getDoubleClickTime()) {
lastTime = currentTime;
handleSingleClick();
if (isInEditorHover(event.x, event.y)) {
pressedInEditorHover = true;
}
}
break;
case SWT.MouseUp:
if (pressedInEditorHover) {
open();
}
pressedInEditorHover = false;
break;
case SWT.MouseMove:
handleMouseMove(event.x, event.y);
break;
case SWT.MouseExit:
Display.getCurrent().asyncExec(new Runnable() {
public void run() {
setShowingHover(false);
setShowingEditorHover(false);
}
});
break;
case SWT.MouseEnter:
handleMouseMove(event.x, event.y);
break;
case SWT.MouseDoubleClick:
handleDoubleClick();
}
}
};
canvas.addListener(SWT.Paint, listener);
canvas.addListener(SWT.Traverse, listener);
canvas.addListener(SWT.FocusIn, listener);
canvas.addListener(SWT.FocusOut, listener);
canvas.addListener(SWT.KeyDown, listener);
canvas.addListener(SWT.MouseDown, listener);
canvas.addListener(SWT.MouseUp, listener);
canvas.addListener(SWT.MouseMove, listener);
canvas.addListener(SWT.MouseExit, listener);
canvas.addListener(SWT.MouseEnter, listener);
canvas.addListener(SWT.MouseDoubleClick, listener);
editor = descriptor.createPropertyEditor(canvas);
if (editor != null)
initPropertyEditor(editor);
updateLabels();
updateEditor();
}
private void initPropertyEditor(final PropertyEditor editor) {
editor.addEditingListener(new IEditingListener() {
public void editingCanceled() {
hideEditor();
update();
}
public void editingFinished() {
changeProperty(editor.getValue());
hideEditor();
update();
}
});
editor.deactivate();
}
private void paintCanvas(GC gc, int px, int py, int pw, int ph) {
if (canvas == null || canvas.isDisposed())
return;
Rectangle r = canvas.getBounds();
Color b1 = canvas.getBackground();
gc.setAntialias(SWT.ON);
gc.setTextAntialias(SWT.ON);
// Draw selection background:
if (isSelected()) {
gc.setBackground(selectedBackground);
gc.fillRoundRectangle(0, 0, r.width, r.height, CORNER, CORNER);
} else if (showingHover) {
int oldAlpha = gc.getAlpha();
gc.setAlpha(96 * oldAlpha / 255);
gc.setBackground(selectedBackground);
gc.fillRoundRectangle(0, 0, r.width, r.height, CORNER, CORNER);
gc.setAlpha(oldAlpha);
}
// Draw edit indicator:
if (showingEditorHover) {
int oldAlpha = gc.getAlpha();
gc.setAlpha(192 * oldAlpha / 255);
gc.setBackground(b1);
gc.fillRoundRectangle(r.width / 2 + HOVER_SHRINK_H, HOVER_SHRINK_V,
r.width / 2 - HOVER_SHRINK_H - HOVER_SHRINK_H,
r.height - HOVER_SHRINK_V - HOVER_SHRINK_V, HOVER_CORNER,
HOVER_CORNER);
gc.setAlpha(oldAlpha);
//
// Control ec = editor == null ? null : editor.getControl();
// if (ec != null && !ec.isDisposed()) {
// Rectangle eb = ec.getBounds();
// gc.setAlpha(192);
// gc.setBackground(b1);
// gc.fillRoundRectangle(eb.x, eb.y, eb.width, eb.height, 8, 8);
// gc.setAlpha(255);
// }
}
// Draw property name label:
gc.setForeground(canvas.getForeground());
gc.setFont(canvas.getFont());
if (nameLayout != null && !nameLayout.isDisposed()) {
Rectangle nb = nameLayout.getBounds();
nameLayout.draw(gc, MARGIN_H, (r.height - nb.height) / 2);
}
// Draw property value repsentating image:
int right = r.width - MARGIN_H;
if (valueImage != null && !valueImage.isDisposed()) {
Rectangle ib = valueImage.getBounds();
int iw, ih;
if (ib.height > r.height) {
ih = r.height;
iw = ib.width * r.height / ib.height;
} else {
ih = ib.height;
iw = ib.width;
}
gc.drawImage(valueImage, 0, 0, ib.width, ib.height, right - iw,
(r.height - ih) / 2, iw, ih);
right = right - iw - 3;
}
// Draw property value label:
if (valueLayout != null && !valueLayout.isDisposed()) {
Rectangle vb = valueLayout.getBounds();
valueLayout.draw(gc, right - vb.width, (r.height - vb.height) / 2);
}
}
private void clearLayoutCache() {
//TODO
}
private void layoutControls() {
Rectangle r = canvas.getBounds();
int hw = Math.max(0, (r.width - MARGIN_H) / 2 - MARGIN_H);
nameLayout.setWidth(Math.max(hw, 1));
valueLayout.setWidth(Math.max(hw, 1));
if (editor != null) {
Control ec = editor.getControl();
int eh = r.height - MARGIN_V - MARGIN_V;
ec.setBounds(r.width - MARGIN_H - hw, (r.height - eh) / 2, hw, eh);
}
// if (border != null) {
// border.dispose();
// border = null;
// }
// border = new Path(Display.getCurrent());
// float left = 0, top = 0, right = r.width, bottom = r.height;
// border.moveTo(right - RADIUS, top);
// border.cubicTo(right - RADIUS_BEND, top, right, top + RADIUS_BEND,
// right, top + RADIUS);
// border.lineTo(right, bottom - RADIUS);
// border.cubicTo(right, bottom - RADIUS_BEND, right - RADIUS_BEND,
// bottom, right - RADIUS, bottom);
// border.lineTo(left + RADIUS, bottom);
// border.cubicTo(left + RADIUS_BEND, bottom, left, bottom - RADIUS_BEND,
// left, bottom - RADIUS);
// border.lineTo(left, top + RADIUS);
// border.cubicTo(left, top + RADIUS_BEND, left + RADIUS_BEND, top, left
// + RADIUS, top);
// border.close();
}
private Point computeCanvasSize(int wHint, int hHint) {
if (wHint < 0 || hHint < 0) {
if (editor != null && editor.getControl() != null
&& !editor.getControl().isDisposed()) {
Point editorSize = editor.getControl()
.computeSize(
wHint < 0 ? SWT.DEFAULT
: Math.max(0,
(wHint - MARGIN_H) / 2
- MARGIN_H - 1),
hHint, true);
if (wHint < 0)
wHint = editorSize.x * 2;
if (hHint < 0)
hHint = editorSize.y;
} else {
if (wHint < 0)
wHint = 100;
if (hHint < 0)
hHint = 1;
}
Rectangle nb = nameLayout.getBounds();
Rectangle vb = valueLayout.getBounds();
hHint = Math.max(hHint, Math.max(nb.height, vb.height)) + MARGIN_V
+ MARGIN_V;
}
return new Point(wHint, hHint);
}
public Control getControl() {
return canvas;
}
public void setFont(Font font) {
this.font = font;
updateWidgets();
}
public void setForeground(Color color) {
this.foreground = color;
updateWidgets();
}
public void setSelectedBackground(Color color) {
this.selectedBackground = color;
updateWidgets();
}
public void setSelectedForeground(Color color) {
this.selectedForeground = color;
updateWidgets();
}
public void setSelectedFont(Font font) {
this.selectedFont = font;
updateWidgets();
}
private void showEditor() {
if (editor != null) {
editor.activate();
}
}
private void hideEditor() {
if (editor != null) {
editor.deactivate();
}
}
private void changeProperty(Object newValue) {
source.setPropertyValue(descriptor.getId(), newValue);
}
public void setFocus() {
if (editor != null && editor.isActivated()) {
editor.setFocus();
} else if (canvas != null && !canvas.isDisposed()) {
canvas.setFocus();
}
}
public void dispose() {
if (editor != null) {
editor.dispose();
editor = null;
}
if (canvas != null && !canvas.isDisposed()) {
canvas.dispose();
}
canvas = null;
if (valueImage != null && !valueImage.isDisposed()) {
valueImage.dispose();
}
valueImage = null;
if (nameLayout != null && !nameLayout.isDisposed()) {
nameLayout.dispose();
}
nameLayout = null;
if (valueLayout != null && !valueLayout.isDisposed()) {
valueLayout.dispose();
}
valueLayout = null;
// if (border != null && !border.isDisposed()) {
// border.dispose();
// }
// border = null;
}
public void update() {
updateWidgets();
updateEditor();
}
public boolean isSelected() {
return selected;
}
public void setSelected(boolean selected) {
boolean oldSelected = this.selected;
this.selected = selected;
updateWidgets();
if (oldSelected != selected) {
if (canvas != null && !canvas.isDisposed()) {
canvas.redraw();
}
}
}
public void open() {
selectSingle();
showEditor();
setShowingEditorHover(false);
setFocus();
}
protected void updateWidgets() {
if (isSelected()) {
updateColorsFonts(selectedForeground, selectedFont);
} else {
updateColorsFonts(foreground, font);
}
updateLabels();
}
private void updateColorsFonts(Color foreground, Font font) {
if (canvas != null && !canvas.isDisposed()) {
canvas.setBackground(canvas.getParent().getBackground());
canvas.setForeground(foreground);
canvas.setFont(font);
if (editor != null) {
editor.setBackground(isSelected() ? selectedBackground
: canvas.getBackground());
editor.setForeground(foreground);
editor.setFont(font);
}
}
}
private void updateLabels() {
Object value = source.getPropertyValue(descriptor.getId());
if (nameLayout != null && !nameLayout.isDisposed()) {
nameLayout.setText(descriptor.getDisplayName());
nameLayout
.setStyle(
new TextStyle(canvas.getFont(),
canvas.getForeground(), null),
0, nameLayout.getText().length());
}
ILabelDescriptor labelDescriptor = descriptor.getLabelDescriptor();
ImageDescriptor image = labelDescriptor == null ? null
: labelDescriptor.getImage(value);
if (valueImage != null) {
valueImage.dispose();
}
valueImage = image == null ? null
: image.createImage(false, Display.getCurrent());
ColorDescriptor color = labelDescriptor == null ? null
: labelDescriptor.getForeground(value);
if (valueColor != null) {
valueColor.dispose();
}
valueColor = color == null ? null
: color.createColor(Display.getCurrent());
FontDescriptor font = labelDescriptor == null ? null
: labelDescriptor.getFont(value);
if (valueFont != null) {
valueFont.dispose();
}
valueFont = font == null ? null : font.createFont(Display.getCurrent());
if (valueLayout != null && !valueLayout.isDisposed()) {
String valueText = labelDescriptor == null
? (value == null ? "" //$NON-NLS-1$
: value.toString())
: labelDescriptor.getText(value);
if (valueText == null)
valueText = ""; //$NON-NLS-1$
valueLayout.setText(valueText);
valueLayout.setStyle(new TextStyle(
valueFont == null ? canvas.getFont() : valueFont,
valueColor == null ? canvas.getForeground() : valueColor,
null), 0, valueText.length());
}
// canvas.layout(true);
canvas.redraw();
}
private void updateEditor() {
if (editor != null) {
editor.setValue(source.getPropertyValue(descriptor.getId()));
}
}
private void selectSingle() {
parent.select(this);
}
private boolean selectNext() {
return parent.select(getNext());
}
private boolean selectPrev() {
return parent.select(getPrevious());
}
protected void handleDoubleClick() {
open();
}
protected void handleSingleClick() {
selectSingle();
setFocus();
}
protected boolean handleKeyTraverse(int traversal) {
if (traversal == SWT.TRAVERSE_RETURN) {
if (editor != null) {
if (editor.isActivated()) {
changeProperty(editor.getValue());
hideEditor();
update();
} else {
open();
}
}
return false;
} else if (traversal == SWT.TRAVERSE_ARROW_PREVIOUS) {
// selectPrev();
return true;
} else if (traversal == SWT.TRAVERSE_ARROW_NEXT) {
// selectNext();
return true;
} else if (traversal == SWT.TRAVERSE_TAB_NEXT) {
// return selectNext();
return true;
} else if (traversal == SWT.TRAVERSE_TAB_PREVIOUS) {
// return selectPrev();
return true;
} else if (traversal == SWT.TRAVERSE_ESCAPE) {
if (editor.isActivated()) {
hideEditor();
update();
return false;
} else {
return true;
}
}
return false;
}
protected void handleKeyPress(int keyCode, int stateMask) {
if (stateMask == 0) {
if (keyCode == SWT.ARROW_DOWN || keyCode == SWT.ARROW_RIGHT) {
selectNext();
} else if (keyCode == SWT.ARROW_UP || keyCode == SWT.ARROW_LEFT) {
selectPrev();
} else if (keyCode == '\t') {
// selectNext();
}
}
}
protected void handleMouseMove(int x, int y) {
setShowingHover(isInHover(x, y));
if (editor != null) {
if (!editor.isActivated()) {
if (isInEditorHover(x, y)) {
setShowingEditorHover(true);
} else {
setShowingEditorHover(false);
}
} else {
setShowingEditorHover(false);
}
}
}
protected boolean isInHover(int x, int y) {
Rectangle r = canvas.getBounds();
return x >= 0 && y >= 0 && x < r.width && y < r.height;
}
protected boolean isInEditorHover(int x, int y) {
Rectangle r = canvas.getBounds();
return x > r.width / 2 && x < r.width && y >= 0 && y < r.height;
}
private void setShowingHover(boolean showing) {
if (showing == this.showingHover)
return;
this.showingHover = showing;
if (canvas != null && !canvas.isDisposed()) {
canvas.redraw();
}
}
private void setShowingEditorHover(boolean showing) {
if (showing == this.showingEditorHover)
return;
this.showingEditorHover = showing;
if (canvas != null && !canvas.isDisposed()) {
if (showing) {
canvas.setCursor(
canvas.getDisplay().getSystemCursor(SWT.CURSOR_HAND));
} else {
canvas.setCursor(null);
}
canvas.redraw();
}
}
public PropertyEditingEntry getPrevious() {
return prev;
}
public PropertyEditingEntry getNext() {
return next;
}
public void setPrevious(PropertyEditingEntry element) {
this.prev = element;
}
public void setNext(PropertyEditingEntry element) {
this.next = element;
}
public void setPopupMenu(Menu menu) {
if (canvas != null && !canvas.isDisposed()) {
canvas.setMenu(menu);
}
}
private void handleCopy() {
String propertyId = descriptor.getId();
Object value = source.getPropertyValue(propertyId);
IPropertyTransfer transfer = parent.getTransfer();
if (transfer != null) {
transfer.setPropertyValueToClipboard(propertyId, value);
}
}
private void handlePaste() {
String propertyId = descriptor.getId();
IPropertyTransfer transfer = parent.getTransfer();
if (transfer != null) {
Object value = transfer.getPropertyValueFromClipboard(propertyId);
source.setPropertyValue(propertyId, value);
}
}
@Override
public boolean isTextOperation() {
return descriptor.isTextOperation();
}
}