[425633] Machine translation suggestions for Babel editor
diff --git a/org.eclipse.babel.editor/.classpath b/org.eclipse.babel.editor/.classpath index 0b1bcf9..c72d35a 100644 --- a/org.eclipse.babel.editor/.classpath +++ b/org.eclipse.babel.editor/.classpath
@@ -2,6 +2,6 @@ <classpath> <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/> <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/> - <classpathentry kind="src" path="src/"/> + <classpathentry kind="src" path="src"/> <classpathentry kind="output" path="target/classes"/> </classpath>
diff --git a/org.eclipse.babel.editor/META-INF/MANIFEST.MF b/org.eclipse.babel.editor/META-INF/MANIFEST.MF index 50521ea..d273d9a 100644 --- a/org.eclipse.babel.editor/META-INF/MANIFEST.MF +++ b/org.eclipse.babel.editor/META-INF/MANIFEST.MF
@@ -36,6 +36,11 @@ org.eclipse.babel.editor.api, org.eclipse.babel.editor.util, org.eclipse.babel.editor.widgets, + org.eclipse.babel.editor.widgets.suggestion, + org.eclipse.babel.editor.widgets.suggestion.exception, + org.eclipse.babel.editor.widgets.suggestion.model, + org.eclipse.babel.editor.widgets.suggestion.provider, org.eclipse.babel.editor.wizards Import-Package: org.eclipse.ui.forms.widgets Bundle-ActivationPolicy: lazy +Bundle-ClassPath: .
diff --git a/org.eclipse.babel.editor/build.properties b/org.eclipse.babel.editor/build.properties index fdad6ae..2b0454a 100644 --- a/org.eclipse.babel.editor/build.properties +++ b/org.eclipse.babel.editor/build.properties
@@ -3,11 +3,9 @@ bin.includes = META-INF/,\ .,\ plugin.xml,\ - icons/,\ plugin.properties,\ messages.properties,\ - CHANGES + CHANGES,\ + icons/ src.includes = icons/,\ - messages.properties,\ - plugin.properties,\ - plugin.xml + messages.properties
diff --git a/org.eclipse.babel.editor/icons/ajax-loader.gif b/org.eclipse.babel.editor/icons/ajax-loader.gif new file mode 100644 index 0000000..4d5b583 --- /dev/null +++ b/org.eclipse.babel.editor/icons/ajax-loader.gif Binary files differ
diff --git a/org.eclipse.babel.editor/icons/sample.gif b/org.eclipse.babel.editor/icons/sample.gif new file mode 100644 index 0000000..34fb3c9 --- /dev/null +++ b/org.eclipse.babel.editor/icons/sample.gif Binary files differ
diff --git a/org.eclipse.babel.editor/plugin.xml b/org.eclipse.babel.editor/plugin.xml index 5d6d349..4aae8f4 100644 --- a/org.eclipse.babel.editor/plugin.xml +++ b/org.eclipse.babel.editor/plugin.xml
@@ -1,6 +1,7 @@ <?xml version="1.0" encoding="UTF-8"?> <?eclipse version="3.2"?> <plugin> + <extension-point id="org.eclipse.babel.editor.suggestion" name="Suggestion Provider Extension Point" schema="schema/org.eclipse.babel.editor.suggestion.exsd"/> <extension point="org.eclipse.ui.editors"> <editor
diff --git a/org.eclipse.babel.editor/schema/org.eclipse.babel.editor.suggestion.exsd b/org.eclipse.babel.editor/schema/org.eclipse.babel.editor.suggestion.exsd new file mode 100644 index 0000000..08b72c3 --- /dev/null +++ b/org.eclipse.babel.editor/schema/org.eclipse.babel.editor.suggestion.exsd
@@ -0,0 +1,102 @@ +<?xml version='1.0' encoding='UTF-8'?> +<!-- Schema file written by PDE --> +<schema targetNamespace="org.eclipse.babel.editor" xmlns="http://www.w3.org/2001/XMLSchema"> +<annotation> + <appInfo> + <meta.schema plugin="org.eclipse.babel.editor" id="org.eclipse.babel.editor.suggestion" name="Suggestion Provider Extension Point"/> + </appInfo> + <documentation> + [Enter description of this extension point.] + </documentation> + </annotation> + + <element name="extension"> + <annotation> + <appInfo> + <meta.element /> + </appInfo> + </annotation> + <complexType> + <choice minOccurs="1" maxOccurs="unbounded"> + <element ref="client"/> + </choice> + <attribute name="point" type="string" use="required"> + <annotation> + <documentation> + + </documentation> + </annotation> + </attribute> + <attribute name="id" type="string"> + <annotation> + <documentation> + + </documentation> + </annotation> + </attribute> + <attribute name="name" type="string"> + <annotation> + <documentation> + + </documentation> + <appInfo> + <meta.attribute translatable="true"/> + </appInfo> + </annotation> + </attribute> + </complexType> + </element> + + <element name="client"> + <complexType> + <attribute name="class" type="string"> + <annotation> + <documentation> + + </documentation> + <appInfo> + <meta.attribute kind="java" basedOn=":org.eclipse.babel.editor.widgets.suggestion.provider.ISuggestionProvider"/> + </appInfo> + </annotation> + </attribute> + </complexType> + </element> + + <annotation> + <appInfo> + <meta.section type="since"/> + </appInfo> + <documentation> + [Enter the first release in which this extension point appears.] + </documentation> + </annotation> + + <annotation> + <appInfo> + <meta.section type="examples"/> + </appInfo> + <documentation> + [Enter extension point usage example here.] + </documentation> + </annotation> + + <annotation> + <appInfo> + <meta.section type="apiinfo"/> + </appInfo> + <documentation> + [Enter API information here.] + </documentation> + </annotation> + + <annotation> + <appInfo> + <meta.section type="implementation"/> + </appInfo> + <documentation> + [Enter information about supplied implementation of this extension point.] + </documentation> + </annotation> + + +</schema>
diff --git a/org.eclipse.babel.editor/src/org/eclipse/babel/editor/i18n/AbstractI18NEntry.java b/org.eclipse.babel.editor/src/org/eclipse/babel/editor/i18n/AbstractI18NEntry.java index f05d8b5..38b2bc5 100755 --- a/org.eclipse.babel.editor/src/org/eclipse/babel/editor/i18n/AbstractI18NEntry.java +++ b/org.eclipse.babel.editor/src/org/eclipse/babel/editor/i18n/AbstractI18NEntry.java
@@ -8,6 +8,7 @@ * Contributors: * Pascal Essiembre - initial API and implementation * Alexej Strelzow - updateKey + * Samir Soyer - passing Locale to NullableText ******************************************************************************/ package org.eclipse.babel.editor.i18n; @@ -32,6 +33,8 @@ import org.eclipse.swt.events.KeyAdapter; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.KeyListener; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; import org.eclipse.swt.events.TraverseEvent; import org.eclipse.swt.events.TraverseListener; import org.eclipse.swt.layout.GridData; @@ -182,7 +185,7 @@ */ private void createTextbox() { textBox = new NullableText(this, SWT.MULTI | SWT.WRAP | SWT.H_SCROLL - | SWT.V_SCROLL | SWT.BORDER); + | SWT.V_SCROLL | SWT.BORDER, locale); textBox.setEnabled(false); textBox.setOrientation(UIUtils.getOrientation(locale)); @@ -212,8 +215,17 @@ } }); - // Handle dirtyness - textBox.addKeyListener(getKeyListener()); + // Handle dirtyness + textBox.addKeyListener(getKeyListener()); + textBox.getTextBox().addModifyListener(new ModifyListener() { + @Override + public void modifyText(ModifyEvent e) { + if (textBox.isDirty()) { + updateModel(); + textBox.setDirty(false); + } + } + }); editor.addChangeListener(msgEditorUpdateKey); }
diff --git a/org.eclipse.babel.editor/src/org/eclipse/babel/editor/plugin/Startup.java b/org.eclipse.babel.editor/src/org/eclipse/babel/editor/plugin/Startup.java index 2d43da3..fa99b05 100644 --- a/org.eclipse.babel.editor/src/org/eclipse/babel/editor/plugin/Startup.java +++ b/org.eclipse.babel.editor/src/org/eclipse/babel/editor/plugin/Startup.java
@@ -10,6 +10,7 @@ ******************************************************************************/ package org.eclipse.babel.editor.plugin; +import org.eclipse.babel.editor.widgets.suggestion.lookup.SuggestionProviderLoader; import org.eclipse.ui.IStartup; /** @@ -22,6 +23,7 @@ * @see org.eclipse.ui.IStartup#earlyStartup() */ public void earlyStartup() { + SuggestionProviderLoader.registerProviders(); // done. // System.out.println("Starting up. " // + "TODO: Register nature with every project and listen for new "
diff --git a/org.eclipse.babel.editor/src/org/eclipse/babel/editor/widgets/NullableText.java b/org.eclipse.babel.editor/src/org/eclipse/babel/editor/widgets/NullableText.java index f500469..4f96de3 100644 --- a/org.eclipse.babel.editor/src/org/eclipse/babel/editor/widgets/NullableText.java +++ b/org.eclipse.babel.editor/src/org/eclipse/babel/editor/widgets/NullableText.java
@@ -7,17 +7,23 @@ * * Contributors: * Pascal Essiembre - initial API and implementation + * Samir Soyer - Suggestion Bubble ******************************************************************************/ package org.eclipse.babel.editor.widgets; +import java.util.Locale; import java.util.Stack; import org.eclipse.babel.editor.util.UIUtils; +import org.eclipse.babel.editor.widgets.suggestion.SuggestionBubble; +import org.eclipse.babel.editor.widgets.suggestion.provider.SuggestionProviderUtils; import org.eclipse.swt.SWT; import org.eclipse.swt.events.FocusListener; import org.eclipse.swt.events.KeyAdapter; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.KeyListener; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; @@ -38,6 +44,9 @@ private final Text text; private final Color defaultColor; private final Color nullColor; + private Locale locale; + private boolean dirty; + private boolean suggestionBubbleOn; private boolean isnull; @@ -60,7 +69,7 @@ /** * Constructor. */ - public NullableText(Composite parent, int style) { + public NullableText(Composite parent, int style, Locale locale) { super(parent, SWT.NONE); text = new Text(this, style); text.setData("UNDO", new Stack<String>()); @@ -78,7 +87,28 @@ setLayoutData(gd); initComponents(); - } + this.locale = locale; + + suggestionBubbleOn = !SuggestionProviderUtils.getSuggetionProviders() + .isEmpty(); + + if (suggestionBubbleOn) { + if (locale != null) { + new SuggestionBubble(text, locale.getLanguage()); + } else { + text.addModifyListener(new ModifyListener() { + @Override + public void modifyText(ModifyEvent e) { + SuggestionBubble.setDefaultText(text.getText()); + } + }); + } + } + } + + public Text getTextBox() { + return this.text; + } public void setOrientation(int orientation) { text.setOrientation(orientation); @@ -86,17 +116,59 @@ public void setText(String text) { isnull = text == null; + + if (locale == null && suggestionBubbleOn) { + SuggestionBubble.setDefaultText(text); + } + if (isnull) { this.text.setText(""); //$NON-NLS-1$x renderNull(); } else { this.text.setText(text); + renderNormal(); } + Stack<String> undoCache = (Stack<String>) this.text.getData("UNDO"); undoCache.push(this.text.getText()); } + /** + * Sets this <code>NullableText</code> to dirty or vice versa + * + * @param dirty + */ + public void setDirty(boolean dirty) { + this.dirty = dirty; + } + + /** + * This method returns whether the content of this + * <code> NullableText</code> have changed since the last save. + * + * @return <code>true</code> if this NullableText is dirty; + * <code>false</code> otherwise. + */ + public boolean isDirty() { + return dirty; + } + + /** + * Applies the string to <code>NullableText</code> and makes it dirty, + * depending on the value of <code> dirty </code> + * + * @param text + * is the string to be applied to <code> NullableText </code> + * @param dirty + * whether setting text should make this + * <code>NullableText</code> dirty. + */ + public void setText(String text, boolean dirty) { + this.dirty = dirty; + setText(text); + } + public String getText() { if (isnull) { return null;
diff --git a/org.eclipse.babel.editor/src/org/eclipse/babel/editor/widgets/suggestion/PartialTranslationDialog.java b/org.eclipse.babel.editor/src/org/eclipse/babel/editor/widgets/suggestion/PartialTranslationDialog.java new file mode 100644 index 0000000..568644c --- /dev/null +++ b/org.eclipse.babel.editor/src/org/eclipse/babel/editor/widgets/suggestion/PartialTranslationDialog.java
@@ -0,0 +1,312 @@ +/******************************************************************************* + * Copyright (c) 2013 Samir Soyer. + * 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: + * Samir Soyer - initial API and implementation + ******************************************************************************/ +package org.eclipse.babel.editor.widgets.suggestion; + +import org.eclipse.babel.editor.widgets.NullableText; +import org.eclipse.babel.editor.widgets.suggestion.exception.SuggestionErrors; +import org.eclipse.jface.dialogs.PopupDialog; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.events.FocusListener; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.graphics.FontData; +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.Button; +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.Label; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; + +/** + * Child dialog of {@link SuggestionBubble}, which lets user to mark a part of + * the suggestion and apply it to {@link Text} + * + * @author Samir Soyer + * + */ +public class PartialTranslationDialog { + + private PopupDialog dialog; + private Shell shell; + private SuggestionBubble parent; + private Composite composite; + private Text textField; + private final String FOOT_NOTE_1 = "Click for focus"; + private final String FOOT_NOTE_2 = "Mark the text, which will be used as translation, then click on 'Apply' button"; + private String infoText; + private String text; + private int orientation; + private boolean win; + private static int SHELL_STYLE; + + /** + * The constructor + * + * @param shell + * is the shell of the SuggestionBubble that is parent of this + * dialog + * @param parent + * is the parent of this dialog. + */ + public PartialTranslationDialog(Shell shell, SuggestionBubble parent) { + this.parent = parent; + this.shell = shell; + + if (System.getProperty("os.name").toLowerCase().contains("windows")) { + SHELL_STYLE = PopupDialog.INFOPOPUP_SHELLSTYLE; + win = true; + } else { + SHELL_STYLE = PopupDialog.HOVER_SHELLSTYLE; + win = false; + } + + } + + private void createDialog(final int shellStyle) { + // int shellStyle = PopupDialog.INFOPOPUPRESIZE_SHELLSTYLE; + boolean takeFocusOnOpen = false; + boolean persistSize = false; + boolean persistLocation = false; + boolean showDialogMenu = false; + boolean showPersistActions = false; + String titleText = null; + dialog = new PopupDialog(shell, shellStyle, takeFocusOnOpen, + persistSize, persistLocation, showDialogMenu, + showPersistActions, titleText, infoText) { + + @Override + protected Control createDialogArea(Composite parent) { + composite = (Composite) super.createDialogArea(parent); + + composite.setLayout(new GridLayout(2, false)); + + final Button button = new Button(composite, SWT.PUSH); + button.setText("Apply"); + button.setEnabled(false); + button.addSelectionListener(new SelectionListener() { + + @Override + public void widgetSelected(SelectionEvent e) { + NullableText nText = (NullableText) PartialTranslationDialog.this.parent + .getTextField().getParent(); + + nText.setText( + PartialTranslationDialog.this.parent + .getTextField().getText() + + textField.getSelectionText(), true); + + PartialTranslationDialog.this.parent.dispose(); + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + } + + }); + + Label label = new Label(composite, SWT.NONE); + label.setText("Selected translation"); + + FontData fontData = label.getFont().getFontData()[0]; + Font font = new Font(label.getDisplay(), new FontData( + fontData.getName(), fontData.getHeight(), SWT.BOLD)); + label.setFont(font); + + // Invisible separator + new Label(composite, SWT.NONE); + + textField = new Text(composite, SWT.V_SCROLL | SWT.WRAP + | SWT.MULTI | SWT.READ_ONLY | orientation); + textField.setText(text); + textField.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, + true, 2, 1)); + textField.addListener(SWT.MouseUp, new Listener() { + @Override + public void handleEvent(Event event) { + Text text = (Text) event.widget; + + String selection = text.getSelectionText(); + + if (selection.length() > 0 + && !SuggestionErrors.contains(textField + .getText())) { + button.setEnabled(true); + } else { + button.setEnabled(false); + } + } + }); + + Listener scrollBarListener = new Listener() { + @Override + public void handleEvent(Event event) { + Text t = (Text) event.widget; + Rectangle r1 = t.getClientArea(); + Rectangle r2 = t.computeTrim(r1.x, r1.y, r1.width, + r1.height); + Point p = t.computeSize(composite.getSize().x, + SWT.DEFAULT, true); + t.getVerticalBar().setVisible(r2.height <= p.y); + } + }; + + textField.addListener(SWT.Resize, scrollBarListener); + + return composite; + } + + @Override + protected void adjustBounds() { + super.adjustBounds(); + + Point start = parent.getCurrentLocation(); + Point size = parent.getSize(); + + int x = start.x + size.x; + int y = start.y; + int screenWidth = Display.getCurrent().getBounds().width; + + if (screenWidth - x <= 200) { + x = start.x - 450; + } + + getShell().setLocation(x, y); + + if (screenWidth - x < 450) { + getShell().setSize(screenWidth - x, 200); + } else { + getShell().setSize(450, 200); + } + } + + @Override + protected void configureShell(Shell shell) { + super.configureShell(shell); + + if (win) { + shell.addFocusListener(new FocusListener() { + + @Override + public void focusGained(FocusEvent e) { + if (shellStyle == INFOPOPUPRESIZE_SHELLSTYLE + || dialog == null) { + return; + } + dialog.close(); + infoText = FOOT_NOTE_2; + createDialog(PopupDialog.INFOPOPUPRESIZE_SHELLSTYLE); + dialog.open(); + dialog.getShell().setFocus(); + } + + @Override + public void focusLost(FocusEvent e) { + } + }); + } + } + }; + } + + /** + * @return location of this dialog relative to display + */ + public Point getLocation() { + return dialog.getShell().toDisplay(1, 1); + } + + /** + * @return size if this dialog + */ + public Point getSize() { + return dialog.getShell().getSize(); + } + + /** + * Creates a new dialog. If it is already created, it updates its text. + * + * @param text + * is the string to displayed in the dialog + * @param orientation + * is the text alignment for the specific language/locale, which + * should be either <code>SWT.LEFT_TO_RIGHT</code> or + * <code>SWT.RIGHT_TO_LEFT</code>. + */ + public void openDialog(String text, int orientation) { + if (SuggestionErrors.contains(text)) { + return; + } + + this.text = text; + this.orientation = orientation; + if (dialog != null && dialog.getShell() != null) { + textField.setText(text); + } else { + infoText = FOOT_NOTE_1; + createDialog(SHELL_STYLE); + dialog.open(); + } + + } + + /** + * Disposes this dialog. + */ + public void dispose() { + if (dialog != null) { + dialog.close(); + } + } + + /** + * @return true if mouse cursor is in the bounds of dialog, otherwise false. + */ + public boolean isCursorInsideDialog() { + if (dialog == null || dialog.getShell() == null + || dialog.getShell().isDisposed()) { + return false; + } + + Display d = Display.getCurrent(); + if (d == null) { + d = Display.getDefault(); + } + + Point start = dialog.getShell().getLocation(); + Point size = dialog.getShell().getSize(); + Point end = new Point(size.x + start.x, size.y + start.y); + + if ((d.getCursorLocation().x > end.x || d.getCursorLocation().x < start.x) + || (d.getCursorLocation().y > end.y || d.getCursorLocation().y < start.y)) { + return false; + } + return true; + } + + /** + * @return true if dialog is already created and visible, otherwise false. + */ + public boolean isVisible() { + if (dialog != null && dialog.getShell() != null) { + return true; + } + return false; + } +}
diff --git a/org.eclipse.babel.editor/src/org/eclipse/babel/editor/widgets/suggestion/SuggestionBubble.java b/org.eclipse.babel.editor/src/org/eclipse/babel/editor/widgets/suggestion/SuggestionBubble.java new file mode 100644 index 0000000..7dad126 --- /dev/null +++ b/org.eclipse.babel.editor/src/org/eclipse/babel/editor/widgets/suggestion/SuggestionBubble.java
@@ -0,0 +1,824 @@ +/******************************************************************************* + * Copyright (c) 2013 Samir Soyer. + * 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: + * Samir Soyer - initial API and implementation + ******************************************************************************/ +package org.eclipse.babel.editor.widgets.suggestion; + +import java.awt.Frame; +import java.util.ArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.swing.ImageIcon; +import javax.swing.JLabel; + +import org.eclipse.babel.editor.widgets.NullableText; +import org.eclipse.babel.editor.widgets.suggestion.exception.SuggestionErrors; +import org.eclipse.babel.editor.widgets.suggestion.filter.SuggestionFilter; +import org.eclipse.babel.editor.widgets.suggestion.model.Suggestion; +import org.eclipse.babel.editor.widgets.suggestion.provider.ISuggestionProvider; +import org.eclipse.babel.editor.widgets.suggestion.provider.ISuggestionProviderListener; +import org.eclipse.babel.editor.widgets.suggestion.provider.SuggestionProviderUtils; +import org.eclipse.jface.bindings.keys.KeySequence; +import org.eclipse.jface.bindings.keys.KeyStroke; +import org.eclipse.jface.bindings.keys.SWTKeySupport; +import org.eclipse.jface.dialogs.PopupDialog; +import org.eclipse.jface.viewers.ArrayContentProvider; +import org.eclipse.jface.viewers.DoubleClickEvent; +import org.eclipse.jface.viewers.IDoubleClickListener; +import org.eclipse.jface.viewers.ILabelProviderListener; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.ITableLabelProvider; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.awt.SWT_AWT; +import org.eclipse.swt.custom.ScrolledComposite; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.events.FocusListener; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.KeyListener; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; +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.Display; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.TableItem; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.IWorkbenchCommandConstants; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.keys.IBindingService; + +/** + * Auto complete pop-up dialog that displays translation suggestions from a + * given text to a target language. Detecting the source language depends on the + * implementation of {@link ISuggestionProvider}. + * + * @author Samir Soyer + */ +public class SuggestionBubble implements ISuggestionProviderListener { + + private PopupDialog dialog; + private TableViewer tableViewer; + private Text text; + private Shell shell; + private Point caret; + private SuggestionFilter suggestionFilter; + private Composite composite; + private ScrolledComposite scrollComposite; + private Label noSug; + private PartialTranslationDialog partialTranslationDialog; + private ArrayList<Suggestion> suggestions; + // private static ArrayList<ISuggestionProvider> suggestionProviders; + private String targetLanguage; + private String oldDefaultText = ""; + private static String defaultText; + private static boolean win; + private String SRC_LANG = "EN"; + private static int SHELL_STYLE; + private final String CONTENT_ASSIST; + private final Level LOG_LEVEL = Level.INFO; + + private static final Logger LOGGER = Logger + .getLogger(SuggestionBubble.class.getName()); + + /** + * Constructor + * + * @param parent + * is the parent {@link Text} object, to which SuggestionBubble + * will be added. + * @param targetLanguage + * is the language, to which the + * {@link SuggestionBubble.defaultText} will be translated + */ + public SuggestionBubble(Text parent, String targetLanguage) { + shell = parent.getShell(); + text = parent; + this.targetLanguage = targetLanguage; + + suggestionFilter = new SuggestionFilter(); + suggestions = new ArrayList<Suggestion>(); + + String srcLang = System + .getProperty("tapiji.translator.default.language"); + if (srcLang != null) { + SRC_LANG = srcLang.substring(0, 2).toUpperCase(); + } + + if (System.getProperty("os.name").toLowerCase().contains("windows")) { + SHELL_STYLE = PopupDialog.INFOPOPUPRESIZE_SHELLSTYLE; + win = true; + } else { + SHELL_STYLE = PopupDialog.HOVER_SHELLSTYLE; + win = false; + } + + // MessagesEditorPlugin.getDefault().getBundle(). + // getEntry("glossary.xml").getPath() + // System.out.println("install path "+MessagesEditorPlugin.getDefault().getBundle().getEntry("/").getPath()+"glossary.xml"); + + SuggestionProviderUtils.addSuggestionProviderUpdateListener(this); + + /* + * Read shortcut of content assist (code completion) directly from + * org.eclipse.ui.IWorkbenchCommandConstants.EDIT_CONTENT_ASSIST and + * save it to CONTENT_ASSIST final variable + */ + IBindingService bindingService = (IBindingService) PlatformUI + .getWorkbench().getAdapter(IBindingService.class); + + CONTENT_ASSIST = bindingService + .getBestActiveBindingFormattedFor(IWorkbenchCommandConstants.EDIT_CONTENT_ASSIST); + + init(); + } + + /** + * @return default text i.e source text that is being localized + */ + public static String getDefaultText() { + return defaultText; + } + + /** + * @param defaultText + * is the source text that is being localized. + */ + public static void setDefaultText(String defaultText) { + SuggestionBubble.defaultText = defaultText; + } + + private void updateSuggestions() { + if (!oldDefaultText.equals(defaultText)) { + + ArrayList<ISuggestionProvider> providers = SuggestionProviderUtils + .getSuggetionProviders(); + + LOGGER.log(LOG_LEVEL, "size of suggestions: " + suggestions.size() + + ", size of providers: " + providers.size()); + + suggestions.clear(); + + final Display d = Display.getCurrent(); + for (final ISuggestionProvider provider : providers) { + + Thread fetch = new Thread() { + Composite loadingCircle; + + @Override + public void run() { + if (!d.isDisposed()) { + d.asyncExec(new Runnable() { + @Override + public void run() { + + // Show circle + if (!composite.isDisposed()) { + loadingCircle = createLoadingCircle(); + } + } + }); + } + + // Do the work + suggestions.add(provider.getSuggestion(defaultText, + targetLanguage)); + + if (!d.isDisposed()) { + d.asyncExec(new Runnable() { + @Override + public void run() { + + // remove laoding circle + if (!composite.isDisposed()) { + loadingCircle.dispose(); + tableViewer.setInput(suggestions + .toArray()); + pack(); + composite.layout(); + } + } + }); + } + } + }; + fetch.start(); + } + oldDefaultText = defaultText; + } else { + tableViewer.setInput(suggestions.toArray()); + pack(); + composite.layout(); + } + } + + /** + * @return true if this SuggestionBubble is created, i.e if it is visible, + * false otherwise. + */ + public boolean isCreated() { + if (dialog != null && dialog.getShell() != null) { + return true; + } else { + return false; + } + } + + /** + * Disposes this SuggestionBubble. + */ + public void dispose() { + if (dialog != null) + dialog.close(); + } + + private void init() { + // Focus handling simulation for non-Windows systems + if (!win) { + shell.getDisplay().addFilter(SWT.MouseDown, new Listener() { + @Override + public void handleEvent(Event event) { + + if (isCursorInsideTextField() + && text.getText().length() == 0 && !isCreated() + && text.isFocusControl()) { + suggestionFilter.setSearchText(""); + createDialog(); + tableViewer.refresh(); + } else { + if (partialTranslationDialog != null) { + if (!partialTranslationDialog + .isCursorInsideDialog() + && !isCursorInsideDialog()) { + dispose(); + } + } else { + if (!isCursorInsideDialog()) { + dispose(); + } + } + } + } + }); + } + + // shell resize listener to dispose suggestion bubble + shell.addListener(SWT.Resize, new Listener() { + public void handleEvent(Event e) { + if (dialog != null && dialog.getShell() != null) { + dialog.close(); + } + } + }); + + // shell move listener + shell.addListener(SWT.Move, new Listener() { + public void handleEvent(Event e) { + if (dialog != null && dialog.getShell() != null) { + dialog.close(); + } + } + }); + + // get ScrolledComposite + ScrolledComposite scrolledComposite = (ScrolledComposite) text + .getParent().getParent().getParent().getParent(); + // scroll listener + scrolledComposite.getVerticalBar().addSelectionListener( + new SelectionListener() { + @Override + public void widgetSelected(SelectionEvent e) { + if (dialog != null && dialog.getShell() != null) { + dialog.close(); + } + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + } + + }); + + // ModifyListener + text.addModifyListener(new ModifyListener() { + + @Override + public void modifyText(ModifyEvent e) { + + recalculatePosition(); + + if (dialog != null && dialog.getShell() != null + && !tableViewer.getControl().isDisposed()) { + suggestionFilter.setSearchText(text.getText().trim()); + tableViewer.refresh(); + + if (tableViewer.getTable().getItemCount() == 0) { + if (noSug == null || noSug.isDisposed()) { + noSug = new Label(composite, SWT.NONE); + noSug.setText("No suggestions available"); + noSug.moveAbove(tableViewer.getControl()); + noSug.setBackground(new Color(shell.getDisplay(), + 255, 255, 225)); + composite.layout(); + } + } else { + if (noSug != null && !noSug.isDisposed()) { + tableViewer.getTable().setSelection(0); + noSug.dispose(); + composite.layout(); + } + } + + suggestionFilter.setSearchText(""); + } + } + + }); + + // KeyListener + text.addKeyListener(new KeyListener() { + + @Override + public void keyPressed(KeyEvent e) { + if ((e.keyCode == SWT.CR || e.keyCode == SWT.LF) + && (dialog != null && dialog.getShell() != null) + && tableViewer.getTable().getSelectionIndex() != -1) { + e.doit = false; + } + + int accelerator = SWTKeySupport + .convertEventToUnmodifiedAccelerator(e); + KeyStroke keyStroke = SWTKeySupport + .convertAcceleratorToKeyStroke(accelerator); + KeySequence sequence = KeySequence.getInstance(keyStroke); + + if (sequence.format().equals(CONTENT_ASSIST)) { + + if (isCreated()) { + if (noSug != null && !noSug.isDisposed()) { + noSug.dispose(); + composite.layout(); + } + suggestionFilter.setSearchText(""); + tableViewer.refresh(); + tableViewer.getTable().setSelection(0); + } else { + createDialog(); + suggestionFilter.setSearchText(text.getText().trim()); + tableViewer.refresh(); + } + e.doit = false; + } + } + + @Override + public void keyReleased(KeyEvent e) { + + if (dialog == null || dialog.getShell() == null) { + return; + } + + if (e.keyCode == SWT.ESC) { + dialog.close(); + return; + } + + // Changing selection with keyboard arrows and applying + // translation with enter + int currentSelectionIndex = tableViewer.getTable() + .getSelectionIndex(); + + if (e.keyCode == SWT.ARROW_DOWN) { + if (currentSelectionIndex >= tableViewer.getTable() + .getItemCount() - 1) { + tableViewer.getTable().setSelection(0); + } else { + tableViewer.getTable().setSelection( + currentSelectionIndex + 1); + } + } + + if (e.keyCode == SWT.ARROW_UP) { + if (currentSelectionIndex <= 0) { + tableViewer.getTable().setSelection( + tableViewer.getTable().getItemCount() - 1); + } else { + tableViewer.getTable().setSelection( + currentSelectionIndex - 1); + } + } + + if (e.keyCode == SWT.CR || e.keyCode == SWT.LF) { + applySuggestion(text); + } + } + }); + + // FocusListener + text.addFocusListener(new FocusListener() { + + @Override + public void focusGained(FocusEvent e) { + + if (win && !isCreated() && text.getText().length() == 0) { + suggestionFilter.setSearchText(""); + createDialog(); + tableViewer.refresh(); + } + } + + @Override + public void focusLost(FocusEvent e) { + if (win && dialog != null && !isCursorInsideDialog()) { + dialog.close(); + } + } + }); + + //MouseListener for Windows systems + if (win) { + text.addMouseListener(new MouseListener() { + + @Override + public void mouseDoubleClick(MouseEvent e) { + // Nothing to do + } + + @Override + public void mouseDown(MouseEvent e) { + // Nothing to do + } + + @Override + public void mouseUp(MouseEvent e) { + + if (caret != null) { + if (dialog != null + && !caret.equals(text.getCaretLocation())) { + dialog.close(); + caret = text.getCaretLocation(); + } + } else { + caret = text.getCaretLocation(); + } + + if (partialTranslationDialog != null + && !partialTranslationDialog.isCursorInsideDialog()) { + partialTranslationDialog.dispose(); + } + } + }); + } + } + + private void createDialog() { + boolean takeFocusOnOpen = false; + boolean persistSize = false; + boolean persistLocation = false; + boolean showDialogMenu = false; + boolean showPersistActions = false; + String titleText = "Suggestions (" + SRC_LANG + " > " + + targetLanguage.toUpperCase() + ")"; + String infoText = "Ctrl+Space to display all suggestions"; + dialog = new PopupDialog(shell, SHELL_STYLE, takeFocusOnOpen, + persistSize, persistLocation, showDialogMenu, + showPersistActions, titleText, infoText) { + + @Override + protected Control createDialogArea(Composite parent) { + scrollComposite = new ScrolledComposite( + (Composite) super.createDialogArea(parent), + SWT.V_SCROLL | SWT.H_SCROLL); + scrollComposite.setLayoutData(new GridData(GridData.FILL_BOTH)); + scrollComposite.setExpandVertical(true); + scrollComposite.setExpandHorizontal(true); + + GridLayout gl = new GridLayout(1, true); + gl.verticalSpacing = 0; + composite = new Composite(scrollComposite, SWT.NONE); + composite.setLayout(gl); + scrollComposite.setContent(composite); + + tableViewer = new TableViewer(composite, SWT.NO_SCROLL); + tableViewer.getTable().setLayoutData( + new GridData(GridData.FILL, SWT.TOP, true, false)); + + tableViewer.setContentProvider(new ArrayContentProvider()); + tableViewer.setLabelProvider(new ITableLabelProvider() { + + @Override + public Image getColumnImage(Object arg0, int arg1) { + Suggestion s = (Suggestion) arg0; + return s.getIcon(); + } + + @Override + public String getColumnText(Object element, int index) { + return ((Suggestion) element).getText(); + + } + + @Override + public void addListener(ILabelProviderListener listener) { + // nothing to do + } + + @Override + public void dispose() { + // nothing to do + } + + @Override + public boolean isLabelProperty(Object arg0, String arg1) { + return true; + } + + @Override + public void removeListener(ILabelProviderListener arg0) { + // nothing to do + } + }); + + tableViewer.addFilter(suggestionFilter); + + tableViewer.addDoubleClickListener(new DoubleClickListener() { + + @Override + public void doubleClick(DoubleClickEvent event) { + applySuggestion(text); + } + + }); + + tableViewer + .addSelectionChangedListener(new ISelectionChangedListener() { + + @Override + public void selectionChanged( + SelectionChangedEvent event) { + if (tableViewer.getTable().getSelection().length > 0) { + partialTranslationDialog.openDialog( + tableViewer.getTable() + .getSelection()[0] + .getText(), text + .getOrientation()); + } + } + }); + + // For Windows 7 + // Set background color of column line + // tableViewer.getTable().addListener(SWT.EraseItem, new + // Listener() { + // @Override + // public void handleEvent(Event event) { + // event.gc.setBackground(new Color(shell.getDisplay(), 255, + // 255, 225)); + // event.gc.fillRectangle(event.getBounds()); + // } + // }); + + tableViewer.getTable().setSelection(0); + return scrollComposite; + } + + @Override + protected void adjustBounds() { + super.adjustBounds(); + + Point point = text.getCaretLocation(); + + getShell().setLocation(text.toDisplay(1, 1).x + point.x + 5, + text.toDisplay(1, 1).y + point.y + 20); + + getShell().setSize(450, 200); + } + + }; + dialog.open(); + + partialTranslationDialog = new PartialTranslationDialog( + dialog.getShell(), this); + + dialog.getShell().addListener(SWT.Resize, new Listener() { + public void handleEvent(Event e) { + partialTranslationDialog.dispose(); + } + }); + + updateSuggestions(); + } + + private void pack() { + Point temp = new Point(0, 0); + Point max = new Point(0, 0); + + for (TableItem item : tableViewer.getTable().getItems()) { + temp.x = item.getBounds().width; + temp.y = item.getBounds().height; + if (temp.x > max.x) { + max.x = temp.x; + } + max.y = max.y + temp.y; + } + scrollComposite.setMinSize(max); + } + + private boolean isCursorInsideTextField() { + if (text.isDisposed()) { + return false; + } + + Display d = Display.getCurrent(); + if (d == null) { + d = Display.getDefault(); + } + + Point start = text.getLocation(); + start = text.toDisplay(start.x, start.y); + Point size = text.getSize(); + Point end = new Point(size.x + start.x, size.y + start.y); + + if ((d.getCursorLocation().x > end.x || d.getCursorLocation().x < start.x) + || (d.getCursorLocation().y > end.y || d.getCursorLocation().y < start.y)) { + return false; + } + return true; + } + + private Composite createLoadingCircle() { + // Create loading cicle + Composite loadingCircle = new Composite(composite, SWT.EMBEDDED + | SWT.NO_BACKGROUND); + Frame frame = SWT_AWT.new_Frame(loadingCircle); + ImageIcon imageIcon = new ImageIcon(this.getClass().getResource( + "/icons/ajax-loader.gif")); + JLabel label = new JLabel(imageIcon); + frame.add(label); + frame.setBackground(new java.awt.Color(255, 255, 225)); + + GridData gd = new GridData(GridData.BEGINNING, SWT.TOP, false, false); + gd.heightHint = 16; + gd.widthHint = 16; + loadingCircle.setLayoutData(gd); + return loadingCircle; + } + + /** + * @return parent {@link Text} of this SuggestionBubble. + */ + public Text getTextField() { + return text; + } + + /** + * @return language of this SuggestionBubble, to which default text is being + * translated + */ + public String getTargetLanguage() { + return targetLanguage; + } + + private void recalculatePosition() { + caret = text.getCaretLocation(); + + if (dialog != null && dialog.getShell() != null) { + + int oldCaretX = getCurrentLocation().x - (text.toDisplay(1, 1).x) + - 5; + int oldCaretY = getCurrentLocation().y - (text.toDisplay(1, 1).y) + - 20; + + int newCaretX = caret.x; + int newCaretY = caret.y; + + setLocation(getCurrentLocation().x + (newCaretX - oldCaretX), + getCurrentLocation().y + (newCaretY - oldCaretY)); + + } + } + + /** + * @return current location of the SuggestionBubble on the screen. + */ + public Point getCurrentLocation() { + if (dialog != null && dialog.getShell() != null) { + return dialog.getShell().getLocation(); + // return dialog.getShell().toDisplay(1, 1); + } + + return new Point(0, 0); + } + + /** + * @return size of the SuggestionBubble + */ + public Point getSize() { + return dialog.getShell().getSize(); + } + + private void setLocation(int x, int y) { + if (dialog != null && dialog.getShell() != null) + dialog.getShell().setLocation(new Point(x, y)); + } + + private void applySuggestion(Text text) { + if (tableViewer.getTable().getSelectionIndex() == -1) { + return; + } + IStructuredSelection selection = (IStructuredSelection) tableViewer + .getSelection(); + Suggestion suggestion = (Suggestion) selection.getFirstElement(); + + String s = suggestion.getText(); + + // Filter out [(].*[% match)] + if (s.lastIndexOf("(") != -1) { + s = s.substring(0, s.lastIndexOf("(") - 1); + } + + if (SuggestionErrors.contains(s)) { + // Ignore call + return; + } + + ((NullableText) text.getParent()).setText(s, true); + + dialog.close(); + } + + private boolean isCursorInsideDialog() { + if (dialog == null || dialog.getShell() == null) { + return false; + } + + Display d = Display.getCurrent(); + if (d == null) { + d = Display.getDefault(); + } + + Point start = dialog.getShell().getLocation(); + Point size = dialog.getShell().getSize(); + Point end = new Point(size.x + start.x, size.y + start.y); + + if ((d.getCursorLocation().x > end.x || d.getCursorLocation().x < start.x) + || (d.getCursorLocation().y > end.y || d.getCursorLocation().y < start.y)) { + return false; + } + return true; + } + + /** + * @see org.eclipse.babel.editor.widgets.suggestion.provider. + * ISuggestionProviderListener#suggestionProviderUpdated(org.eclipse.babel + * .editor.widgets.suggestion.provider.ISuggestionProvider) + */ + @Override + public void suggestionProviderUpdated(ISuggestionProvider provider) { + LOGGER.log(LOG_LEVEL, "provider :" + + provider.getClass().getSimpleName() + + ", size of suggestions: " + suggestions.size()); + + for (int i = 0; i < suggestions.size(); i++) { + Suggestion sug = suggestions.get(i); + if (sug.getProvider().equals(provider)) { + suggestions.set(i, + provider.getSuggestion(defaultText, targetLanguage)); + } + } + + if (tableViewer != null && !tableViewer.getTable().isDisposed()) { + tableViewer.setInput(suggestions); + } + } +} + +/** + * Implements {@link IDoubleClickListener} + * + * @author Samir Soyer + * + */ +abstract class DoubleClickListener implements IDoubleClickListener { +} \ No newline at end of file
diff --git a/org.eclipse.babel.editor/src/org/eclipse/babel/editor/widgets/suggestion/exception/InvalidConfigurationSetting.java b/org.eclipse.babel.editor/src/org/eclipse/babel/editor/widgets/suggestion/exception/InvalidConfigurationSetting.java new file mode 100644 index 0000000..7d622ab --- /dev/null +++ b/org.eclipse.babel.editor/src/org/eclipse/babel/editor/widgets/suggestion/exception/InvalidConfigurationSetting.java
@@ -0,0 +1,24 @@ +/******************************************************************************* + * Copyright (c) 2013 Samir Soyer. + * 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: + * Samir Soyer - initial API and implementation + ******************************************************************************/ +package org.eclipse.babel.editor.widgets.suggestion.exception; + +public class InvalidConfigurationSetting extends Exception { + + /** + * + */ + private static final long serialVersionUID = 1L; + + public InvalidConfigurationSetting(String message) { + super(message); + } + +}
diff --git a/org.eclipse.babel.editor/src/org/eclipse/babel/editor/widgets/suggestion/exception/SuggestionErrors.java b/org.eclipse.babel.editor/src/org/eclipse/babel/editor/widgets/suggestion/exception/SuggestionErrors.java new file mode 100644 index 0000000..32eef52 --- /dev/null +++ b/org.eclipse.babel.editor/src/org/eclipse/babel/editor/widgets/suggestion/exception/SuggestionErrors.java
@@ -0,0 +1,67 @@ +/******************************************************************************* + * Copyright (c) 2013 Samir Soyer. + * 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: + * Samir Soyer - initial API and implementation + ******************************************************************************/ +package org.eclipse.babel.editor.widgets.suggestion.exception; + +/** + * This class contains all the error messages that can be used by + * {@link org.eclipse.babel.editor.widgets.suggestion.provider.ISuggestionProvider} + * + * @author Samir Soyer + * + */ +public class SuggestionErrors { + + /** + * to be used, in case suggestion provider can't provide a suggestion + */ + public final static String NO_SUGESTION_ERR = "No suggestions available"; + /** + * to be used, in case an error occurs that is related to Internet + * (Connection / protocol error, etc.) + */ + public final static String CONNECTION_ERR = "Connection error, check your internet connection"; + /** + * to be used, in case suggestion provider doesn't support a specific + * language + */ + public final static String LANG_NOT_SUPPORT_ERR = "Language not supported"; + /** + * to be used, in case quota for maximum allowed translations were exceeded. + */ + public final static String QUOTA_EXCEEDED = "Translation quota has been exceeded"; + /** + * to be used, in case selected glossary file can not be read. + */ + public final static String INVALID_GLOSSARY = "Invalid glossary file"; + /** + * to be used, in case no glossary file is selected. + */ + public final static String NO_GLOSSARY_FILE = "Open a glossary to see suggestions"; + + /** + * Checks whether a string is contained in this class, i.e whether the + * string is an error message. + * + * @param s + * is the string to check. + * @return true if string is a error message, otherwise false. + */ + public static boolean contains(String s) { + if (s.equals(SuggestionErrors.LANG_NOT_SUPPORT_ERR) + || s.equals(SuggestionErrors.CONNECTION_ERR) + || s.equals(SuggestionErrors.NO_SUGESTION_ERR) + || s.equals(SuggestionErrors.QUOTA_EXCEEDED) + || s.equals(SuggestionErrors.NO_GLOSSARY_FILE)) { + return true; + } + return false; + } +}
diff --git a/org.eclipse.babel.editor/src/org/eclipse/babel/editor/widgets/suggestion/filter/SuggestionFilter.java b/org.eclipse.babel.editor/src/org/eclipse/babel/editor/widgets/suggestion/filter/SuggestionFilter.java new file mode 100644 index 0000000..0719e22 --- /dev/null +++ b/org.eclipse.babel.editor/src/org/eclipse/babel/editor/widgets/suggestion/filter/SuggestionFilter.java
@@ -0,0 +1,60 @@ +/******************************************************************************* + * Copyright (c) 2013 Samir Soyer. + * 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: + * Samir Soyer - initial API and implementation + ******************************************************************************/ +package org.eclipse.babel.editor.widgets.suggestion.filter; + +import org.eclipse.babel.editor.widgets.suggestion.exception.SuggestionErrors; +import org.eclipse.babel.editor.widgets.suggestion.model.Suggestion; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerFilter; + +/** + * Filter class for {@link org.eclipse.jface.viewers.TableViewer.TableViewer} in + * {@link org.eclipse.babel.editor.widgets.suggestion.SuggestionBubble} + * + * @author Samir Soyer + * + */ +public class SuggestionFilter extends ViewerFilter { + + private String searchString; + + /** + * Sets the text that is going to be checked, whether it matches suggestions + * in the tableviewer + * + * @param s + * is the text to be searched for + */ + public void setSearchText(String s) { + + this.searchString = ".*" + s + ".*"; + + } + + /** + * @see org.eclipse.jface.viewers.ViewerFilter#select(org.eclipse.jface.viewers.Viewer, + * java.lang.Object, java.lang.Object) + */ + @Override + public boolean select(Viewer viewer, Object parentElement, Object element) { + if (searchString == null || searchString.length() == 0) { + return true; + } + + Suggestion s = (Suggestion) element; + if (s.getText().toLowerCase().matches(searchString.toLowerCase()) + || SuggestionErrors.contains(s.getText())) { + return true; + } + + return false; + } +} \ No newline at end of file
diff --git a/org.eclipse.babel.editor/src/org/eclipse/babel/editor/widgets/suggestion/lookup/SuggestionProviderLoader.java b/org.eclipse.babel.editor/src/org/eclipse/babel/editor/widgets/suggestion/lookup/SuggestionProviderLoader.java new file mode 100644 index 0000000..a74ae1c --- /dev/null +++ b/org.eclipse.babel.editor/src/org/eclipse/babel/editor/widgets/suggestion/lookup/SuggestionProviderLoader.java
@@ -0,0 +1,75 @@ +/******************************************************************************* + * Copyright (c) 2013 Samir Soyer. + * 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: + * Samir Soyer - initial API and implementation + ******************************************************************************/ +package org.eclipse.babel.editor.widgets.suggestion.lookup; + +import org.eclipse.babel.editor.widgets.suggestion.provider.ISuggestionProvider; +import org.eclipse.babel.editor.widgets.suggestion.provider.SuggestionProviderUtils; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IConfigurationElement; +import org.eclipse.core.runtime.IExtensionRegistry; +import org.eclipse.core.runtime.ISafeRunnable; +import org.eclipse.core.runtime.Platform; +import org.eclipse.core.runtime.SafeRunner; + +/** + * Look up for suggestion providers, which implements the extension point + * {@literal "org.eclipselabs.tapiji.translator.suggestion"}. + * @author Samir Soyer + * + */ +public class SuggestionProviderLoader { + private static final String ISUGGESTIONPROVIDER_ID = + "org.eclipse.babel.editor.suggestion"; + + /** + * Finds all the suggestion providers resp. extensions that implement + * {@code org.eclipselabs.tapiji.translator.suggestion} extension point. + * Then registers them at + * {@link org.eclipse.babel.editor.widgets. + * suggestion.provider.SuggestionProviderUtils} + */ + public static void registerProviders() { + IExtensionRegistry registry = Platform.getExtensionRegistry(); + + IConfigurationElement[] config = + registry.getConfigurationElementsFor(ISUGGESTIONPROVIDER_ID); + + try { + for (IConfigurationElement e : config) { + final Object o = + e.createExecutableExtension("class"); + if (o instanceof ISuggestionProvider) { + executeExtension(o); + } + } + } catch (CoreException ex) { + //TODO logging + // System.out.println(ex.getMessage()); + } + } + + private static void executeExtension(final Object o) { + ISafeRunnable runnable = new ISafeRunnable() { + @Override + public void handleException(Throwable e) { + //TODO logging + // System.out.println("Exception in extension"); + } + + @Override + public void run() throws Exception { + ISuggestionProvider provider = ((ISuggestionProvider) o); + SuggestionProviderUtils.addSuggestionProvider(provider); + } + }; + SafeRunner.run(runnable); + } +}
diff --git a/org.eclipse.babel.editor/src/org/eclipse/babel/editor/widgets/suggestion/model/Suggestion.java b/org.eclipse.babel.editor/src/org/eclipse/babel/editor/widgets/suggestion/model/Suggestion.java new file mode 100644 index 0000000..a79bc98 --- /dev/null +++ b/org.eclipse.babel.editor/src/org/eclipse/babel/editor/widgets/suggestion/model/Suggestion.java
@@ -0,0 +1,90 @@ +/******************************************************************************* + * Copyright (c) 2013 Samir Soyer. + * 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: + * Samir Soyer - initial API and implementation + ******************************************************************************/ +package org.eclipse.babel.editor.widgets.suggestion.model; + +import org.eclipse.babel.editor.widgets.suggestion.provider.ISuggestionProvider; +import org.eclipse.swt.graphics.Image; + +/** + * Encapsulates text of the suggestion and icon of the suggestion provider, + * which provides the respective translation. + * + * @author Samir Soyer + * + */ +public class Suggestion { + + private Image icon; + private String text; + private ISuggestionProvider provider; + + /** + * @param icon + * is the image of suggestion provider which provides the + * translation of the text + * @param text + * is the translated suggestion + */ + public Suggestion(Image icon, String text, ISuggestionProvider provider) { + this.icon = icon; + this.text = text; + this.provider = provider; + } + + /** + * @return Image object of the suggestion provider which provides the + * suggestion + */ + public Image getIcon() { + return icon; + } + + /** + * @param icon + * Image object of the suggestion provider which provides the + * suggestion + */ + public void setIcon(Image icon) { + this.icon = icon; + } + + /** + * @return translated text, i.e suggestion + */ + public String getText() { + return text; + } + + /** + * @param text + * is the translated text, i.e suggestion + */ + public void setText(String text) { + this.text = text; + } + + /** + * @return {@link ISuggestionProvider} that provides this suggestion + */ + public ISuggestionProvider getProvider() { + return provider; + } + + /** + * @param provider + * is the {@link ISuggestionProvider} that provides this + * suggestion + */ + public void setProvider(ISuggestionProvider provider) { + this.provider = provider; + } + +}
diff --git a/org.eclipse.babel.editor/src/org/eclipse/babel/editor/widgets/suggestion/provider/ISuggestionProvider.java b/org.eclipse.babel.editor/src/org/eclipse/babel/editor/widgets/suggestion/provider/ISuggestionProvider.java new file mode 100644 index 0000000..be0e339 --- /dev/null +++ b/org.eclipse.babel.editor/src/org/eclipse/babel/editor/widgets/suggestion/provider/ISuggestionProvider.java
@@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright (c) 2013 Samir Soyer. + * 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: + * Samir Soyer - initial API and implementation + ******************************************************************************/ +package org.eclipse.babel.editor.widgets.suggestion.provider; + +import java.util.Map; + +import org.eclipse.babel.editor.widgets.suggestion.exception.InvalidConfigurationSetting; +import org.eclipse.babel.editor.widgets.suggestion.model.Suggestion; + +/** + * Interface for the suggestion providers which should implement {@link + * ISuggestionProvider.getSuggestion()} method to return provided suggestion + * + * @author Samir Soyer + * @author Martin Reiterer - Added suggestion provider configuration methods + * + */ +public interface ISuggestionProvider { + + /** + * Returns translation of the original text to a given language + * + * @param original + * is the untranslated string + * @param targetLanguage + * is the language, to which the original text will be translated + * @return translation of original text + */ + Suggestion getSuggestion(String original, String targetLanguage); + + /** + * Returns a list of all configuration settings of the suggestion provider + * + * @return The list of active configuration settings + */ + Map<String, ISuggestionProviderConfigurationSetting> getAllConfigurationSettings(); + + /** + * Allows to update one particular configuration setting + * + * @param setting + * The configuration Setting of type + * {@link ISuggestionProviderConfigurationSetting} + */ + void updateConfigurationSetting(String configurationId, + ISuggestionProviderConfigurationSetting setting) + throws InvalidConfigurationSetting; +}
diff --git a/org.eclipse.babel.editor/src/org/eclipse/babel/editor/widgets/suggestion/provider/ISuggestionProviderConfigurationSetting.java b/org.eclipse.babel.editor/src/org/eclipse/babel/editor/widgets/suggestion/provider/ISuggestionProviderConfigurationSetting.java new file mode 100644 index 0000000..73c1175 --- /dev/null +++ b/org.eclipse.babel.editor/src/org/eclipse/babel/editor/widgets/suggestion/provider/ISuggestionProviderConfigurationSetting.java
@@ -0,0 +1,32 @@ +/******************************************************************************* + * Copyright (c) 2013 Samir Soyer. + * 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: + * Samir Soyer - initial API and implementation + ******************************************************************************/ +package org.eclipse.babel.editor.widgets.suggestion.provider; + +/** + * Interface for suggestion provider configuration settings. Classes that + * implements this interface can represent the configuration as any type of + * object, which depends on the implementation of + * {@code updateConfigurationSetting()} method in {@link ISuggestionProvider}. + * + * @author Samir Soyer + * + */ +public interface ISuggestionProviderConfigurationSetting { + + /** + * @return configuration setting in any type of object. Note: implementation + * of {@code updateConfigurationSetting()} method in + * {@link ISuggestionProvider} should be able to handle the returned + * object from this method. + */ + Object getConfigurationSetting(); + +}
diff --git a/org.eclipse.babel.editor/src/org/eclipse/babel/editor/widgets/suggestion/provider/ISuggestionProviderListener.java b/org.eclipse.babel.editor/src/org/eclipse/babel/editor/widgets/suggestion/provider/ISuggestionProviderListener.java new file mode 100644 index 0000000..653c31c --- /dev/null +++ b/org.eclipse.babel.editor/src/org/eclipse/babel/editor/widgets/suggestion/provider/ISuggestionProviderListener.java
@@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2013 Samir Soyer. + * 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: + * Samir Soyer - initial API and implementation + ******************************************************************************/ +package org.eclipse.babel.editor.widgets.suggestion.provider; + +/** + * Listener interface for {@link ISuggestionProvider}s. Defines the method to + * call when an update event occurs. + * + * @author Samir Soyer + * + */ +public interface ISuggestionProviderListener { + + /** + * This method will be called after a {@link ISuggestionProvider} is + * updated. e.q if resource of a suggestion provider is updated after + * creating the object. + * + * @param provider + * is the suggestion provider which was updated + */ + public void suggestionProviderUpdated(ISuggestionProvider provider); +}
diff --git a/org.eclipse.babel.editor/src/org/eclipse/babel/editor/widgets/suggestion/provider/StringConfigurationSetting.java b/org.eclipse.babel.editor/src/org/eclipse/babel/editor/widgets/suggestion/provider/StringConfigurationSetting.java new file mode 100644 index 0000000..ee8c36f --- /dev/null +++ b/org.eclipse.babel.editor/src/org/eclipse/babel/editor/widgets/suggestion/provider/StringConfigurationSetting.java
@@ -0,0 +1,63 @@ +/******************************************************************************* + * Copyright (c) 2013 Samir Soyer. + * 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: + * Samir Soyer - initial API and implementation + ******************************************************************************/ +package org.eclipse.babel.editor.widgets.suggestion.provider; + +/** + * This class contains configuration setting for {@link ISuggestionProvider} in + * string format, i.e object that contains the configuration setting is a + * string. + * + * @author Samir Soyer + * + */ +public class StringConfigurationSetting implements + ISuggestionProviderConfigurationSetting { + + private String config; + + /** + * Constructor + * + * @param config + * is the string that contains the configuration, e.g + * {@code "/home/xyz/file.xml"} + */ + public StringConfigurationSetting(String config) { + super(); + this.config = config; + } + + /** + * @return configuration as string + */ + public String getConfig() { + return config; + } + + /** + * @param config + * is the string that contains the configuration, e.g + * {@code "/home/xyz/file.xml" + */ + public void setConfig(String config) { + this.config = config; + } + + /** + * @see org.eclipse.babel.editor.widgets .suggestion.provider + * .ISuggestionProviderConfigurationSetting#getConfigurationSetting() + */ + @Override + public Object getConfigurationSetting() { + return config; + } + +}
diff --git a/org.eclipse.babel.editor/src/org/eclipse/babel/editor/widgets/suggestion/provider/SuggestionProviderUtils.java b/org.eclipse.babel.editor/src/org/eclipse/babel/editor/widgets/suggestion/provider/SuggestionProviderUtils.java new file mode 100644 index 0000000..a17d748 --- /dev/null +++ b/org.eclipse.babel.editor/src/org/eclipse/babel/editor/widgets/suggestion/provider/SuggestionProviderUtils.java
@@ -0,0 +1,106 @@ +/******************************************************************************* + * Copyright (c) 2013 Samir Soyer. + * 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: + * Samir Soyer - initial API and implementation + ******************************************************************************/ +package org.eclipse.babel.editor.widgets.suggestion.provider; + +import java.util.ArrayList; + +import org.eclipse.babel.editor.widgets.suggestion.exception.InvalidConfigurationSetting; + +/** + * This class contains a list of all suggestion providers. + * {@link org.eclipse.babel.editor.widgets.suggestion.SuggestionBubble} gets its + * suggestion providers from this class, therefore the ones that should be used + * in SuggestionBubble must be registered in this class by calling {@link + * SuggestionProviderUtils.addSuggestionProvider()} method + * + * @author Samir Soyer + * + */ +public class SuggestionProviderUtils { + private static ArrayList<ISuggestionProvider> providers = new ArrayList<ISuggestionProvider>(); + private static ArrayList<ISuggestionProviderListener> listeners = new ArrayList<ISuggestionProviderListener>(); + + /** + * Adds suggestion provider object to the list + * + * @param provider + * is the suggestion provider to be registered + */ + public static void addSuggestionProvider(ISuggestionProvider provider) { + if (!providers.contains(provider)) { + providers.add(provider); + } + } + + /** + * Removes a specific {@link ISuggestionProvider} from the list of + * suggestion providers. + * + * @param provider + * is the {@link ISuggestionProvider} to be removed from the list + */ + public static void removeSuggestionProvider(ISuggestionProvider provider) { + providers.remove(provider); + } + + /** + * @return all the registered suggestion providers + */ + public static ArrayList<ISuggestionProvider> getSuggetionProviders() { + return providers; + } + + /** + * Adds a new suggestion provider listener, which calls {@link + * ISuggestionProviderListener.suggestionProviderUpdated()} method, when a + * suggestion provider object is updated + * + * @param listener + * is the object, whose {@link + * ISuggestionProviderListener.suggestionProviderUpdated()} will + * be called, when the suggestion provider is updated + */ + public static void addSuggestionProviderUpdateListener( + ISuggestionProviderListener listener) { + listeners.add(listener); + } + + /** + * This method is to call after updating a provider + */ + public static void fireSuggestionProviderUpdated( + ISuggestionProvider provider) { + for (ISuggestionProviderListener listener : listeners) { + listener.suggestionProviderUpdated(provider); + } + } + + /** + * Allows to update one particular configuration setting + * + * @param setting + * The configuration Setting of type + * {@link ISuggestionProviderConfigurationSetting} + */ + public static void updateConfigurationSetting(String configurationId, + ISuggestionProviderConfigurationSetting setting) + throws InvalidConfigurationSetting { + + for (ISuggestionProvider provider : providers) { + if (provider.getAllConfigurationSettings().containsKey( + configurationId)) { + provider.updateConfigurationSetting(configurationId, setting); + fireSuggestionProviderUpdated(provider); + } + } + + } +}