| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package org.apache.uima.caseditor.editor; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.apache.uima.cas.CAS; |
| import org.apache.uima.cas.Type; |
| import org.apache.uima.cas.TypeSystem; |
| import org.apache.uima.cas.text.AnnotationFS; |
| import org.eclipse.jface.dialogs.PopupDialog; |
| import org.eclipse.jface.viewers.ILabelProvider; |
| import org.eclipse.jface.viewers.ILabelProviderListener; |
| import org.eclipse.jface.viewers.IOpenListener; |
| import org.eclipse.jface.viewers.ISelection; |
| import org.eclipse.jface.viewers.ITreeContentProvider; |
| import org.eclipse.jface.viewers.OpenEvent; |
| import org.eclipse.jface.viewers.StructuredSelection; |
| import org.eclipse.jface.viewers.TreeViewer; |
| import org.eclipse.jface.viewers.Viewer; |
| import org.eclipse.jface.viewers.ViewerFilter; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.events.KeyEvent; |
| import org.eclipse.swt.events.KeyListener; |
| import org.eclipse.swt.events.MouseEvent; |
| import org.eclipse.swt.events.MouseMoveListener; |
| import org.eclipse.swt.graphics.Image; |
| import org.eclipse.swt.graphics.Point; |
| import org.eclipse.swt.layout.GridData; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Control; |
| import org.eclipse.swt.widgets.Label; |
| import org.eclipse.swt.widgets.Shell; |
| import org.eclipse.swt.widgets.Text; |
| import org.eclipse.swt.widgets.Tree; |
| import org.eclipse.swt.widgets.TreeItem; |
| |
| /** |
| * This is a lightweight popup dialog which creates an annotation of the chosen type. |
| */ |
| class QuickTypeSelectionDialog extends PopupDialog { |
| |
| /** The editor. */ |
| private final AnnotationEditor editor; |
| |
| /** The filter text. */ |
| private Text filterText; |
| |
| /** The shortcut type map. */ |
| private Map<Character, Type> shortcutTypeMap = new HashMap<>(); |
| |
| /** The type shortcut map. */ |
| private Map<Type, Character> typeShortcutMap = new HashMap<>(); |
| |
| /** |
| * Initializes the current instance. |
| * |
| * @param parent |
| * the parent |
| * @param editor |
| * the editor |
| */ |
| QuickTypeSelectionDialog(Shell parent, AnnotationEditor editor) { |
| super(parent, PopupDialog.INFOPOPUPRESIZE_SHELLSTYLE, true, true, true, false, true, null, |
| null); |
| |
| this.editor = editor; |
| |
| // key shortcuts are assigned automatically to types, the shortcut |
| // mapping may change if the type system is modified |
| |
| String shortcutsString = "qwertzuiopasdfghjklyxcvbnm1234567890"; |
| |
| Set<Character> shortcuts = new HashSet<>(); |
| |
| for (int i = 0; i < shortcutsString.length(); i++) { |
| shortcuts.add(shortcutsString.charAt(i)); |
| } |
| |
| List<Type> types = new ArrayList<>(); |
| Collections.addAll(types, getTypes()); |
| types.sort(new Comparator<Type>() { |
| @Override |
| public int compare(Type o1, Type o2) { |
| return o1.getName().compareTo(o2.getName()); |
| } |
| }); |
| |
| // Try to create mappings with first letter of the type name as shortcut |
| for (Iterator<Type> it = types.iterator(); it.hasNext();) { |
| |
| Type type = it.next(); |
| |
| String name = type.getShortName(); |
| |
| Character candidateChar = Character.toLowerCase(name.charAt(0)); |
| |
| if (shortcuts.contains(candidateChar)) { |
| putShortcut(candidateChar, type); |
| |
| shortcuts.remove(candidateChar); |
| it.remove(); |
| } |
| } |
| |
| // Try to create mappings with second letter of the type name as shortcut |
| for (Iterator<Type> it = types.iterator(); it.hasNext();) { |
| |
| Type type = it.next(); |
| |
| String name = type.getShortName(); |
| |
| if (name.length() > 2) { |
| Character candidateChar = Character.toLowerCase(name.charAt(1)); |
| |
| if (shortcuts.contains(candidateChar)) { |
| putShortcut(candidateChar, type); |
| |
| shortcuts.remove(candidateChar); |
| it.remove(); |
| } |
| } |
| } |
| |
| // Now assign letters to the remaining types |
| for (Iterator<Type> it = types.iterator(); it.hasNext() && shortcuts.size() > 0;) { |
| |
| Character candidateChar = shortcuts.iterator().next(); |
| |
| putShortcut(candidateChar, it.next()); |
| |
| shortcuts.remove(candidateChar); |
| it.remove(); |
| } |
| } |
| |
| /** |
| * Put shortcut. |
| * |
| * @param shortcut |
| * the shortcut |
| * @param type |
| * the type |
| */ |
| private void putShortcut(Character shortcut, Type type) { |
| shortcutTypeMap.put(shortcut, type); |
| typeShortcutMap.put(type, shortcut); |
| } |
| |
| /** |
| * Gets the types. |
| * |
| * @return the types |
| */ |
| private Type[] getTypes() { |
| |
| TypeSystem typeSystem = editor.getDocument().getCAS().getTypeSystem(); |
| |
| List<Type> types = typeSystem |
| .getProperlySubsumedTypes(typeSystem.getType(CAS.TYPE_NAME_ANNOTATION)); |
| |
| types.add(typeSystem.getType(CAS.TYPE_NAME_ANNOTATION)); |
| |
| return types.toArray(new Type[types.size()]); |
| } |
| |
| /** |
| * Annotate and close. |
| * |
| * @param annotationType |
| * the annotation type |
| */ |
| private void annotateAndClose(Type annotationType) { |
| if (annotationType != null) { |
| Point textSelection = editor.getSelection(); |
| |
| AnnotationFS annotation = editor.getDocument().getCAS().createAnnotation(annotationType, |
| textSelection.x, textSelection.y); |
| |
| editor.getDocument().addFeatureStructure(annotation); |
| |
| if (annotation.getType().equals(editor.getAnnotationMode())) { |
| editor.setAnnotationSelection(annotation); |
| } |
| } |
| |
| QuickTypeSelectionDialog.this.close(); |
| } |
| |
| @Override |
| protected Control createDialogArea(Composite parent) { |
| Composite composite = (Composite) super.createDialogArea(parent); |
| |
| // TODO: focus always goes to the text box, but should |
| // go to the Tree control, find out why, can SWT.NO_FOCUS be used |
| // to fix it ? |
| |
| filterText = new Text(composite, SWT.NO_FOCUS); |
| filterText.setBackground(parent.getBackground()); |
| filterText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); |
| |
| Label separator = new Label(composite, SWT.SEPARATOR | SWT.HORIZONTAL | SWT.LINE_DOT); |
| separator.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); |
| |
| final TreeViewer typeTree = new TreeViewer(composite, SWT.SINGLE | SWT.V_SCROLL); |
| typeTree.getControl() |
| .setLayoutData(new GridData(GridData.FILL_HORIZONTAL | GridData.FILL_VERTICAL)); |
| |
| typeTree.getControl().setFocus(); |
| |
| filterText.addKeyListener(new KeyListener() { |
| |
| @Override |
| public void keyPressed(KeyEvent e) { |
| if (e.keyCode == SWT.ARROW_DOWN || e.keyCode == SWT.ARROW_UP) { |
| typeTree.getControl().setFocus(); |
| |
| Tree tree = (Tree) typeTree.getControl(); |
| |
| if (tree.getItemCount() > 0) { |
| |
| tree.setSelection(tree.getItem(0)); |
| } |
| } |
| } |
| |
| @Override |
| public void keyReleased(KeyEvent e) { |
| typeTree.refresh(false); |
| } |
| }); |
| |
| typeTree.setContentProvider(new ITreeContentProvider() { |
| |
| @Override |
| public Object[] getChildren(Object parentElement) { |
| return null; |
| } |
| |
| @Override |
| public Object getParent(Object element) { |
| return null; |
| } |
| |
| @Override |
| public boolean hasChildren(Object element) { |
| return false; |
| } |
| |
| @Override |
| public Object[] getElements(Object inputElement) { |
| return (Type[]) inputElement; |
| } |
| |
| @Override |
| public void dispose() { |
| } |
| |
| @Override |
| public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { |
| } |
| }); |
| |
| typeTree.setFilters(new ViewerFilter[] { new ViewerFilter() { |
| @Override |
| public boolean select(Viewer viewer, Object parentElement, Object element) { |
| |
| // check if the string from the filterText is contained in the type name |
| Type type = (Type) element; |
| |
| return type.getName().contains(filterText.getText()); |
| } |
| } }); |
| |
| typeTree.setLabelProvider(new ILabelProvider() { |
| |
| @Override |
| public Image getImage(Object element) { |
| return null; |
| } |
| |
| @Override |
| public String getText(Object element) { |
| |
| Type type = (Type) element; |
| |
| Character key = typeShortcutMap.get(type); |
| |
| if (key != null) { |
| return "[" + key + "] " + type.getShortName(); |
| } else { |
| return type.getShortName(); |
| } |
| } |
| |
| @Override |
| public void addListener(ILabelProviderListener listener) { |
| |
| } |
| |
| @Override |
| public void dispose() { |
| } |
| |
| @Override |
| public boolean isLabelProperty(Object element, String property) { |
| return false; |
| } |
| |
| @Override |
| public void removeListener(ILabelProviderListener listener) { |
| } |
| }); |
| |
| typeTree.getControl().addKeyListener(new KeyListener() { |
| |
| @Override |
| public void keyPressed(KeyEvent e) { |
| Type type = shortcutTypeMap.get(Character.toLowerCase(e.character)); |
| |
| if (type != null) { |
| annotateAndClose(type); |
| } |
| } |
| |
| @Override |
| public void keyReleased(KeyEvent e) { |
| } |
| }); |
| |
| typeTree.getControl().addMouseMoveListener(new MouseMoveListener() { |
| |
| @Override |
| public void mouseMove(MouseEvent e) { |
| |
| Tree tree = (Tree) typeTree.getControl(); |
| |
| TreeItem item = tree.getItem(new Point(e.x, e.y)); |
| |
| if (item != null) { |
| tree.setSelection(item); |
| } |
| } |
| }); |
| |
| // TODO open listener needs a double click, single click should be enough |
| // because there is already a selection below the mouse |
| typeTree.addOpenListener(new IOpenListener() { |
| |
| @Override |
| public void open(OpenEvent event) { |
| StructuredSelection selection = (StructuredSelection) event.getSelection(); |
| |
| annotateAndClose((Type) selection.getFirstElement()); |
| } |
| }); |
| |
| Collection<Type> shownAnnotationTypes = editor.getShownAnnotationTypes(); |
| |
| Type[] types = shownAnnotationTypes.toArray(new Type[shownAnnotationTypes.size()]); |
| |
| typeTree.setInput(types); |
| |
| ISelection modeSelection = new StructuredSelection(new Object[] { editor.getAnnotationMode() }); |
| |
| typeTree.setSelection(modeSelection, true); |
| |
| return composite; |
| } |
| |
| @Override |
| protected Point getInitialSize() { |
| return new Point(250, 300); |
| } |
| } |