/*******************************************************************************
* Copyright (c) 2010 IBM Corporation 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:
* IBM Corporation - initial API and implementation
******************************************************************************/
package org.eclipse.nebula.widgets.bidilayout;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.BidiSegmentEvent;
import org.eclipse.swt.custom.BidiSegmentListener;
import org.eclipse.swt.custom.LineBackgroundListener;
import org.eclipse.swt.custom.MovementListener;
import org.eclipse.swt.custom.ST;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.custom.StyledTextContent;
import org.eclipse.swt.custom.StyledTextPrintOptions;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.events.VerifyListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.LineAttributes;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.internal.BidiUtil;
import org.eclipse.swt.internal.win32.OS;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.printing.Printer;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Caret;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
@SuppressWarnings("restriction")
public class BidiLayout extends Composite {
public static final int LANG_ARABIC = 0x01;
public static final int LANG_HEBREW = 0x0d;
public static final int LANG_ENGLISH = 0x09;
private static final char LRO = '\u202d';
private static final char RLO = '\u202e';
private static final char PDF = '\u202c';
static final String eolStr = "\r\n";
Listener listener;
StyledText styledText;
int bidiLangCode = 0;
int nonBidiLangCode = 0;
boolean isPushMode;
boolean isWidgetReversed;
boolean isAutoPush;
int indxPushSegmentStart = -1;
int lengthPushSegment = 0;
Caret defaultCaret = null;
MenuItem rtlMenuItem;
MenuItem autopushMenuItem;
char[] arrOfIgnoredChars = null;
private int prevLength = 0;
public BidiLayout(Composite parent, int style){
super(parent, 0);
this.setLayout(new FillLayout());
styledText = new StyledText(this, style);
addBidiSegmentListener();
addListeners();
styledText.setMenu(createContextMenu());
}
public void setBidiLang (int lang) {
bidiLangCode = lang;
}
public void setNonBidiLang (int lang) {
nonBidiLangCode = lang;
}
public void setArrOfIgnoredChars(char[] arr){
arrOfIgnoredChars = (char[])arr.clone();
}
private void addListeners() {
listener = new Listener() {
public void handleEvent(Event event) {
switch (event.type) {
case SWT.MouseDown: handleMouseDown(event); break;
case SWT.KeyDown: handleKeyDown(event); break;
}
}
};
addAndReorderListener(SWT.KeyDown, listener);
styledText.addListener(SWT.MouseDown, listener);
addAndReorderListener(SWT.Show, listener);
styledText.addKeyListener(new KeyListener() {
public void keyReleased(KeyEvent keyEvent) {
if ((keyEvent.keyCode == 'u') &&
((keyEvent.stateMask & SWT.CTRL) != 0)){
setPush(true);
keyEvent.doit = false;
}else if ((keyEvent.keyCode == 'o') &&
((keyEvent.stateMask & SWT.CTRL) != 0)) {
setPush(false);
keyEvent.doit = false;
} else if ((keyEvent.keyCode == 't') &&
((keyEvent.stateMask & SWT.CTRL) != 0)){
switchAutoPush();
keyEvent.doit = false;
} else if (isPushMode && (keyEvent.keyCode == SWT.HOME || keyEvent.keyCode==SWT.END)){
int carPos = getCaretOffset();
setPush(false);
setCaretOffset(carPos);
}
}
public void keyPressed(KeyEvent e) {
}
});
}
public void addAndReorderListener(int eventType, Listener listener){
//have to 'reorder' listeners in eventTable. BidiLayout's listener should come first (before StyledText's one)
Listener[] listeners = styledText.getListeners(eventType);
Listener styledTextListener = null;
for (int i=0; i<listeners.length; i++) {
if (listeners[i].getClass().getSimpleName().startsWith("StyledText")){
styledTextListener = listeners[i];
break;
}
}
if (styledTextListener != null){
styledText.removeListener(eventType, styledTextListener);
}
styledText.addListener(eventType, listener);
if (styledTextListener != null){
styledText.addListener(eventType, styledTextListener);
}
}
private Menu createContextMenu(){
Menu menu;
if (styledText.getMenu() == null)
menu = new Menu(styledText);
else
menu = styledText.getMenu();
MenuItem copy = new MenuItem(menu, SWT.CASCADE);
copy.setText("&Copy"); //$NON-NLS-1$
copy.setAccelerator(SWT.CTRL+'c'); //$NON-NLS-1$
copy.addListener(SWT.Selection, new Listener() {
public void handleEvent(Event e) {
copy();
e.doit = false;
}
});
MenuItem cut = new MenuItem(menu, SWT.CASCADE);
cut.setText("Cu&t"); //$NON-NLS-1$
cut.setAccelerator(SWT.CTRL+'x'); //$NON-NLS-1$
cut.addListener(SWT.Selection, new Listener() {
public void handleEvent(Event e) {
cut();
e.doit = false;
}
});
MenuItem paste = new MenuItem(menu, SWT.CASCADE);
paste.setText("&Paste"); //$NON-NLS-1$
paste.setAccelerator(SWT.CTRL+'v'); //$NON-NLS-1$
paste.addListener(SWT.Selection, new Listener() {
public void handleEvent(Event e) {
paste();
e.doit = false;
}
});
rtlMenuItem = new MenuItem(menu, SWT.CHECK);
rtlMenuItem.setText("Right to Left Reading Order"); //$NON-NLS-1$
rtlMenuItem.setAccelerator(SWT.CTRL | SWT.SHIFT); //$NON-NLS-1$
rtlMenuItem.setData("#PopupMenu");
rtlMenuItem.addListener(SWT.Selection, new Listener() {
public void handleEvent(Event e) {
e = createEventForSwithchDir(e);
handleKeyDown(e);
styledText.notifyListeners(SWT.KeyUp, e);
rtlMenuItem.setSelection(isWidgetReversed);
}
private Event createEventForSwithchDir(Event e) {
e.widget = styledText;
e.keyCode = SWT.SHIFT;
if (isWidgetReversed)
e.keyLocation = SWT.LEFT;
else
e.keyLocation = SWT.RIGHT;
e.stateMask = SWT.CTRL;
return e;
}
});
new MenuItem(menu, SWT.SEPARATOR);
autopushMenuItem = new MenuItem(menu, SWT.CHECK);
autopushMenuItem.setText("AutoPush [Ctrl+T]");
autopushMenuItem.addListener(SWT.Selection, new Listener() {
public void handleEvent(Event e) {
switchAutoPush();
}
});
final MenuItem pushon = new MenuItem(menu, SWT.CASCADE/*SWT.CHECK*/);
pushon.setText("Push On [Ctrl+U]");
pushon.addListener(SWT.Selection, new Listener() {
public void handleEvent(Event e) {
setPush(true);
}
});
final MenuItem pushoff = new MenuItem(menu, SWT.CASCADE/*SWT.CHECK*/);
pushoff.setText("Push Off [Ctrl+O]");
pushoff.addListener(SWT.Selection, new Listener() {
public void handleEvent(Event e) {
setPush(false);
}
});
return menu;
}
public void copy() {
styledText.copy();
}
public void cut() {
styledText.cut();
}
public void paste() {
styledText.paste();
}
protected void handleMouseDown (Event event){
if (isPushMode && !isCaretInsidePushSegment()){
int caretPos = styledText.getCaretOffset();
setPush(false);
styledText.setCaretOffset(getUpdatedCaret(caretPos));
}
}
protected void handleKeyDown(Event event) {
if (event.keyCode == SWT.ARROW_RIGHT && isPushMode){
if (!isWidgetReversed && isCursorAtStartPushSegemet())
setPush(false);
else if (isWidgetReversed && isCursorAtEndPushSegemet()){
int newCaretPos = indxPushSegmentStart;
setPush(false);
styledText.setCaretOffset (newCaretPos);
styledText.invokeAction(ST.COLUMN_NEXT);
} else
event.keyCode = SWT.ARROW_LEFT;
} else if (event.keyCode == SWT.ARROW_LEFT && isPushMode){
if (!isWidgetReversed && isCursorAtEndPushSegemet()){
int newCaretPos = indxPushSegmentStart;
setPush(false);
styledText.setCaretOffset (newCaretPos);
styledText.invokeAction(ST.COLUMN_NEXT);
} else if (isWidgetReversed && isCursorAtStartPushSegemet()){
setPush(false);
styledText.invokeAction(ST.COLUMN_NEXT);
} else
event.keyCode = SWT.ARROW_RIGHT;
}else if (isPushMode && (event.keyCode == SWT.HOME || event.keyCode==SWT.END)){
setPush(false);
styledText.notifyListeners(SWT.KeyDown, event);
} else if (event.keyCode == SWT.DEL && isPushMode){
if (!isCursorAtEndPushSegemet()){
lengthPushSegment--;
}else {
event.doit = false;
event.type = SWT.None;
}
} else if (event.keyCode == SWT.BS && isPushMode){
if (!isCursorAtStartPushSegemet()){
lengthPushSegment --;
}else {
setPush(false);
}
}else if ((event.stateMask & SWT.MODIFIER_MASK) == SWT.CTRL && event.keyCode == SWT.SHIFT && BidiUtil.isBidiPlatform()){
if ((!isWidgetReversed && event.keyLocation == SWT.RIGHT) ||
(isWidgetReversed && event.keyLocation == SWT.LEFT)){
switchWidgetDir();
styledText.notifyListeners(SWT.KeyDown, event);
}
} else if (isPushMode && (event.character == SWT.CR) ) {
setPush(false);
styledText.notifyListeners(SWT.KeyDown, event);
} else {
if (isAutoPush && event.character >= ' ' && event.character != SWT.DEL){
handleAutoPush();
}
if ((arrOfIgnoredChars != null) && ((new String(arrOfIgnoredChars)).indexOf(event.character) != -1)){
event.doit = false;
event.type = SWT.None;
return;
}
}
}
private boolean isCursorAtStartPushSegemet() {
int caretOffset = styledText.getCaretOffset();
if (isCaretAtTheLAstLine()) //it is the last line of string
caretOffset --;
//pushSegmentStart was stored when string had similar to widget orientation, therefore it needs to be 'mirrored' for comparison
int mirroredBoundPosition = calculateMirroredPushSegmentStart();
if (mirroredBoundPosition == (caretOffset-1))
return true;
return false;
}
private boolean isCursorAtEndPushSegemet() {
int caretOffset = styledText.getCaretOffset();
if (isCaretAtTheLAstLine())
caretOffset --;
//pushSegmentStart was stored when string had similar to widget orientation, therefore it needs to be 'mirrored' for comparison
int mirroredBoundPosition = calculateMirroredPushSegmentEnd();
if (mirroredBoundPosition == caretOffset)
return true;
return false;
}
private int calculateMirroredCaretPosition(int caretPos) {
LineIndx lineIndx = new LineIndx(styledText.getText(),styledText.getCaretOffset());
return lineIndx.getEndIndx() - (caretPos - lineIndx.getStartIndx());
}
private int calculateMirroredPushSegmentEnd() {
return calculateMirroredCaretPosition(indxPushSegmentStart);
}
private int calculateMirroredPushSegmentStart() {
return calculateMirroredPushSegmentEnd() - lengthPushSegment;
}
private boolean isCaretAtTheLAstLine(){
LineIndx lineIndx = new LineIndx(styledText.getText(),styledText.getCaretOffset());
if (lineIndx.getEndIndx() == styledText.getText().length()-1)
return true;
return false;
}
private boolean isCaretInsidePushSegment(){
int mirroredPushSegmentEnd = calculateMirroredPushSegmentEnd();
int mirroredPushSegmentStart = calculateMirroredPushSegmentStart();
int caretPos = styledText.getCaretOffset();
if ((caretPos >= mirroredPushSegmentStart) && (caretPos <= mirroredPushSegmentEnd))
return true;
return false;
}
protected void switchAutoPush() {
isAutoPush = !isAutoPush;
autopushMenuItem.setSelection(isAutoPush);
}
public boolean isPushMode(){
return isPushMode;
}
public void switchWidgetDir() {
switchWidgetDir(true);
}
public void switchWidgetDir(boolean forceSringReverse) {
isWidgetReversed = !isWidgetReversed;
int carPos = styledText.getCaretOffset();
if (forceSringReverse)
styledText.setText(reverseStr(styledText.getText()));
styledText.setCaretOffset (carPos);
rtlMenuItem.setSelection(isWidgetReversed);
if (isPushMode)
setPush(false);
if (isWidgetReversed)
setBidiKeyboardLanguage();
else
setNonBidiKeyboardLanguage();
}
public void setPush(boolean pushOn) {
if (isPushMode == pushOn)
return;
isPushMode = pushOn;
if (pushOn) {
startPushMode();
}
else {
endPushMode();
indxPushSegmentStart = -1;
lengthPushSegment = 0;
}
}
private void endPushMode() {
styledText.setCaret(defaultCaret);
styledText.setCaret(defaultCaret);
styledText.setText(reverseStr(styledText.getText()));
styledText.setCaretOffset (indxPushSegmentStart + lengthPushSegment);
if (!isWidgetReversed)
setNonBidiKeyboardLanguage();
else
setBidiKeyboardLanguage();
}
public static String reverseStr (String str) {
String resultStr = "";
String orgStr = new String(str);
int i=-1;
while ((i = orgStr.indexOf(eolStr)) != -1){
StringBuffer sb = new StringBuffer(orgStr.substring(0,i));
resultStr += sb.reverse() + eolStr;
orgStr = orgStr.substring(i + eolStr.length());
}
if (orgStr.length()>0){
StringBuffer sb = new StringBuffer(orgStr);
resultStr += sb.reverse();
}
return resultStr;
}
public void addBidiSegmentListener() {
styledText.addBidiSegmentListener(new BidiSegmentListener() {
public void lineGetSegments(BidiSegmentEvent event) {
int length = event.lineText.length();
if ((isPushMode && !isWidgetReversed) ||
(!isPushMode && isWidgetReversed)){
event.segments = new int[] { 0};
event.segmentsChars = new char[] { RLO};
} else {
event.segments = new int[] { 0, length };
event.segmentsChars = new char[] { LRO, PDF };
}
if (isPushMode && (indxPushSegmentStart != -1) && (prevLength< length))
lengthPushSegment++;
prevLength = length;
}
});
}
protected void startPushMode() {
final Image image = new Image (styledText.getDisplay(), 20, 20);
GC gc = new GC (image);
gc.setBackground (styledText.getDisplay().getSystemColor(SWT.COLOR_BLACK));
gc.fillRectangle (0, 0, 20, 20);
gc.setForeground (styledText.getDisplay().getSystemColor(SWT.COLOR_WHITE));
gc.setLineAttributes(new LineAttributes(2));
gc.drawLine (0, 13, gc.getFontMetrics().getAverageCharWidth(), 13);
gc.dispose ();
defaultCaret = styledText.getCaret();
Caret cc = new Caret(styledText, 0);
cc.setImage(image);
styledText.setCaret(cc);
int carOffset = styledText.getCaretOffset();
String str = reverseStr(styledText.getText());
styledText.setText(str);
indxPushSegmentStart = carOffset;
styledText.setCaretOffset(getUpdatedCaret(carOffset));
if (!isWidgetReversed)
setBidiKeyboardLanguage();
else
setNonBidiKeyboardLanguage();
}
private int getUpdatedCaret(int carOffset) {
String str = styledText.getText();
LineIndx lineIndx = new LineIndx(str, carOffset);
int starIndx = lineIndx.getStartIndx();
int endIndx = lineIndx.getEndIndx();
if (endIndx == str.length()-1) //it is the last line of string
carOffset --;
return starIndx + (endIndx - carOffset);
}
private void handleAutoPush(){
if ((isPushMode && !isWidgetReversed && (BidiUtil.getKeyboardLanguage() == BidiUtil.KEYBOARD_NON_BIDI))||
(isPushMode && isWidgetReversed && (BidiUtil.getKeyboardLanguage() == BidiUtil.KEYBOARD_BIDI)))
setPush(false);
else if ((!isPushMode && !isWidgetReversed && (BidiUtil.getKeyboardLanguage() == BidiUtil.KEYBOARD_BIDI)) ||
(!isPushMode && isWidgetReversed && (BidiUtil.getKeyboardLanguage() == BidiUtil.KEYBOARD_NON_BIDI)))
setPush(true);
}
public boolean isWidgetReversed() {
return isWidgetReversed;
}
private void setBidiKeyboardLanguage(){
if (bidiLangCode == 0)
BidiUtil.setKeyboardLanguage(BidiUtil.KEYBOARD_BIDI);
else
setSpecificKeyboardLanguage(bidiLangCode);
}
private void setNonBidiKeyboardLanguage(){
if (nonBidiLangCode == 0)
BidiUtil.setKeyboardLanguage(BidiUtil.KEYBOARD_NON_BIDI);
else
setSpecificKeyboardLanguage(nonBidiLangCode);
}
public static boolean setSpecificKeyboardLanguage(int langCode){
int currentLang = OS.PRIMARYLANGID(OS.LOWORD(OS.GetKeyboardLayout(0)));
if (currentLang == langCode)
return true;
int [] list = getKeyboardLanguageList();
for (int i=0; i<list.length; i++) {
if (langCode == OS.PRIMARYLANGID(OS.LOWORD(list[i]))) {
OS.ActivateKeyboardLayout(list[i], 0);
return true;
}
}
return false;
}
public void setText(String text){
if (isWidgetReversed)
text = reverseStr(text);
styledText.setText(text);
}
public String getText(){
return styledText.getText();
}
public void addSelectionListener(SelectionListener listener) {
styledText.addSelectionListener(listener);
}
public void removeSelectionListener(SelectionListener listener) {
styledText.removeSelectionListener(listener);
}
public void addLineBackgroundListener(LineBackgroundListener listener) {
styledText.addLineBackgroundListener(listener);
}
public void removeLineBackgroundListener(LineBackgroundListener listener) {
styledText.removeLineBackgroundListener(listener);
}
public void addVerifyListener(VerifyListener listener){
styledText.addVerifyListener(listener);
}
public void removeVerifyListener(VerifyListener listener){
styledText.removeVerifyListener(listener);
}
public void addWordMovementListener(MovementListener listener){
styledText.addWordMovementListener(listener);
}
public void removeWordMovementListener(MovementListener listener){
styledText.removeWordMovementListener(listener);
}
public void addModifyListener(ModifyListener modifyListener) {
styledText.addModifyListener(modifyListener);
}
public void removeModifyListener(ModifyListener modifyListener) {
styledText.removeModifyListener(modifyListener);
}
public void redraw() {
styledText.redraw();
}
public int getCharCount() {
return styledText.getCharCount();
}
public Point getLocationAtOffset(int offset){
return styledText.getLocationAtOffset(offset);
}
public int getLineHeight(){
return styledText.getLineHeight();
}
public int getLineHeight(int lineIndex) {
return styledText.getLineHeight(lineIndex);
}
public void redraw(int x, int y, int width, int height, boolean all){
styledText.redraw(x,y,width,height,all);
}
public void redrawRange(int start, int length, boolean clearBackground) {
styledText.redrawRange(start, length, clearBackground);
}
public int getTopIndex(){
return styledText.getTopIndex();
}
public StyledText getStyledText (){
return styledText;
}
public boolean getBlockSelection() {
return styledText.getBlockSelection();
}
public Rectangle getBlockSelectionBounds() {
return styledText.getBlockSelectionBounds();
}
public void setTopIndex(int topIndex) {
styledText.setTopIndex(topIndex);
}
public boolean getEditable(){
return styledText.getEditable();
}
public int styledText(){
return styledText.getHorizontalPixel();
}
public int getLineAtOffset(int offset){
return styledText.getLineAtOffset(offset);
}
public int getHorizontalPixel(){
return styledText.getHorizontalPixel();
}
public int getHorizontalIndex(){
return styledText.getHorizontalIndex();
}
public void setHorizontalIndex(int offset) {
styledText.setHorizontalIndex(offset);
}
public Point getSelection(){
return styledText.getSelection();
}
public void setSelection(int start) {
styledText.setSelection(start);
}
public void setSelection(int start, int end) {
styledText.setSelection(start, end);
}
public void setSelectionRange(int start, int length) {
styledText.setSelectionRange(start, length);
}
public void selectAll(){
styledText.selectAll();
}
public void setStyleRange(StyleRange range) {
styledText.setStyleRange(range);
}
public void setStyleRanges(StyleRange[] ranges) {
styledText.setStyleRanges(ranges);
}
public void showSelection() {
styledText.showSelection();
}
public int getSelectionCount() {
return styledText.getSelectionCount();
}
public Point getSelectionRange(){
return styledText.getSelectionRange();
}
public int[] getSelectionRanges() {
return styledText.getSelectionRanges();
}
public int getTabs() {
return styledText.getTabs();
}
public Rectangle getTextBounds(int start, int end) {
return styledText.getTextBounds(start, end);
}
public int getTopPixel() {
return styledText.getTopPixel();
}
public int getCaretOffset() {
return styledText.getCaretOffset();
}
public void invokeAction(int action){
styledText.invokeAction(action);
}
public Runnable print(Printer printer, StyledTextPrintOptions options) {
return styledText.print(printer, options);
}
public void replaceStyleRanges(int start, int length, StyleRange[] ranges) {
styledText.replaceStyleRanges(start, length, ranges);
}
public void setBlockSelectionBounds(int x, int y, int width, int height) {
styledText.setBlockSelectionBounds(x, y, width, height);
}
public void setBlockSelectionBounds(Rectangle rect) {
styledText.setBlockSelectionBounds(rect);
}
public void setCaretOffset(int offset) {
styledText.setCaretOffset(offset);
}
public void setContent(StyledTextContent newContent) {
styledText.setContent(newContent);
}
public void setDoubleClickEnabled(boolean enable) {
styledText.setDoubleClickEnabled(enable);
}
public void setEditable(boolean editable) {
if (editable)
styledText.setBackground(null);
else
styledText.setBackground(styledText.getParent().getBackground());
styledText.setEditable(editable);
}
public void setEnabled (boolean enabled){
if (enabled)
styledText.setBackground(null);
else
styledText.setBackground(styledText.getParent().getBackground());
styledText.setEnabled(enabled);
super.setEnabled(enabled);
}
public void setBackground (Color color) {
styledText.setBackground(color);
}
//this method was copied from BidiUtil (since it isn't public)
static int [] getKeyboardLanguageList() {
int maxSize = 10;
int [] tempList = new int [maxSize];
int size = OS.GetKeyboardLayoutList(maxSize, tempList);
int [] list = new int [size];
System.arraycopy(tempList, 0, list, 0, size);
return list;
}
private class LineIndx {
private int startIndx = -1;
private int endIndx = -1;
LineIndx (String str, int indx){
startIndx = str.substring(0, indx).lastIndexOf(eolStr);
if (startIndx == -1)
startIndx = 0;
else
startIndx += eolStr.length();
endIndx = str.substring(startIndx).indexOf(eolStr);
if (endIndx == -1)
endIndx = str.substring(startIndx).length()-1;
endIndx += startIndx;
}
public int getStartIndx(){
return startIndx;
}
public int getEndIndx(){
return endIndx;
}
}
}