/**************************************************************************
OmegaT - Computer Assisted Translation (CAT) tool
with fuzzy matching, translation memory, keyword search,
glossaries, and translation leveraging into updated projects.
Copyright (C) 2009 Alex Buloichik
2009 Didier Briel
2010 Wildrich Fourie
2013 Zoltan Bartko
2014 Aaron Madlon-Kay
2015 Yu Tang
Home page: http://www.omegat.org/
Support center: http://groups.yahoo.com/group/OmegaT/
This file is part of OmegaT.
OmegaT is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OmegaT is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
**************************************************************************/
package org.omegat.gui.editor;
import java.awt.Cursor;
import java.awt.Font;
import java.awt.Point;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.swing.JEditorPane;
import javax.swing.JPopupMenu;
import javax.swing.KeyStroke;
import javax.swing.text.AbstractDocument;
import javax.swing.text.BadLocationException;
import javax.swing.text.BoxView;
import javax.swing.text.ComponentView;
import javax.swing.text.Element;
import javax.swing.text.IconView;
import javax.swing.text.MutableAttributeSet;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledEditorKit;
import javax.swing.text.Utilities;
import javax.swing.text.View;
import javax.swing.text.ViewFactory;
import org.omegat.core.Core;
import org.omegat.core.CoreEvents;
import org.omegat.core.data.ProtectedPart;
import org.omegat.core.data.SourceTextEntry;
import org.omegat.gui.editor.autocompleter.AutoCompleter;
import org.omegat.gui.shortcuts.PropertiesShortcuts;
import org.omegat.util.StringUtil;
import org.omegat.util.gui.DockingUI;
import org.omegat.util.gui.StaticUIUtils;
import org.omegat.util.gui.Styles;
/**
* Changes of standard JEditorPane implementation for support custom behavior.
*
* @author Alex Buloichik (alex73mail@gmail.com)
* @author Didier Briel
* @author Wildrich Fourie
* @author Zoltan Bartko
*/
@SuppressWarnings("serial")
public class EditorTextArea3 extends JEditorPane {
private final static KeyStroke KEYSTROKE_CONTEXT_MENU = PropertiesShortcuts.getEditorShortcuts()
.getKeyStroke("editorContextMenu");
private final static KeyStroke KEYSTROKE_NEXT = PropertiesShortcuts.getEditorShortcuts()
.getKeyStroke("editorNextSegment");
private final static KeyStroke KEYSTROKE_PREV = PropertiesShortcuts.getEditorShortcuts()
.getKeyStroke("editorPrevSegment");
private final static KeyStroke KEYSTROKE_NEXT_NOT_TAB = PropertiesShortcuts.getEditorShortcuts()
.getKeyStroke("editorNextSegmentNotTab");
private final static KeyStroke KEYSTROKE_PREV_NOT_TAB = PropertiesShortcuts.getEditorShortcuts()
.getKeyStroke("editorPrevSegmentNotTab");
private final static KeyStroke KEYSTROKE_INSERT_LF = PropertiesShortcuts.getEditorShortcuts()
.getKeyStroke("editorInsertLineBreak");
private final static KeyStroke KEYSTROKE_SELECT_ALL = PropertiesShortcuts.getEditorShortcuts()
.getKeyStroke("editorSelectAll");
private final static KeyStroke KEYSTROKE_SWITCH_ORIENTATION = PropertiesShortcuts.getEditorShortcuts()
.getKeyStroke("editorSwitchOrientation");
private final static KeyStroke KEYSTROKE_DELETE_PREV_TOKEN = PropertiesShortcuts.getEditorShortcuts()
.getKeyStroke("editorDeletePrevToken");
private final static KeyStroke KEYSTROKE_DELETE_NEXT_TOKEN = PropertiesShortcuts.getEditorShortcuts()
.getKeyStroke("editorDeleteNextToken");
private final static KeyStroke KEYSTROKE_FIRST_SEG = PropertiesShortcuts.getEditorShortcuts()
.getKeyStroke("editorFirstSegment");
private final static KeyStroke KEYSTROKE_LAST_SEG = PropertiesShortcuts.getEditorShortcuts()
.getKeyStroke("editorLastSegment");
private final static KeyStroke KEYSTROKE_SKIP_NEXT_TOKEN = PropertiesShortcuts.getEditorShortcuts()
.getKeyStroke("editorSkipNextToken");
private final static KeyStroke KEYSTROKE_SKIP_PREV_TOKEN = PropertiesShortcuts.getEditorShortcuts()
.getKeyStroke("editorSkipPrevToken");
private final static KeyStroke KEYSTROKE_SKIP_NEXT_TOKEN_SEL = PropertiesShortcuts.getEditorShortcuts()
.getKeyStroke("editorSkipNextTokenWithSelection");
private final static KeyStroke KEYSTROKE_SKIP_PREV_TOKEN_SEL = PropertiesShortcuts.getEditorShortcuts()
.getKeyStroke("editorSkipPrevTokenWithSelection");
private final static KeyStroke KEYSTROKE_TOGGLE_CURSOR_LOCK = PropertiesShortcuts.getEditorShortcuts()
.getKeyStroke("editorToggleCursorLock");
/** Undo Manager to store edits */
protected final TranslationUndoManager undoManager = new TranslationUndoManager(this);
protected final EditorController controller;
protected final List<PopupMenuConstructorInfo> popupConstructors = new ArrayList<PopupMenuConstructorInfo>();
protected String currentWord;
protected AutoCompleter autoCompleter = new AutoCompleter(this);
/**
* Whether or not we are confining the cursor to the editable part of the
* text area. The user can optionally allow the caret to roam freely.
*
* @see #checkAndFixCaret(boolean)
*/
protected boolean lockCursorToInputArea = true;
public EditorTextArea3(EditorController controller) {
this.controller = controller;
setEditorKit(new StyledEditorKit() {
public ViewFactory getViewFactory() {
return factory3;
}
protected void createInputAttributes(Element element, MutableAttributeSet set) {
set.removeAttributes(set);
EditorController c = EditorTextArea3.this.controller;
try {
c.m_docSegList[c.displayedEntryIndex].createInputAttributes(element, set);
} catch (Exception ex) {
}
}
});
addMouseListener(mouseListener);
addCaretListener(e -> {
try {
int start = EditorUtils.getWordStart(EditorTextArea3.this, e.getMark());
int end = EditorUtils.getWordEnd(EditorTextArea3.this, e.getMark());
if (end - start <= 0) {
// word not defined
return;
}
String newWord = getText(start, end - start);
if (!newWord.equals(currentWord)) {
currentWord = newWord;
CoreEvents.fireEditorNewWord(newWord);
}
} catch (BadLocationException ex) {
ex.printStackTrace();
}
});
setToolTipText("");
setDragEnabled(true);
setForeground(Styles.EditorColor.COLOR_FOREGROUND.getColor());
setCaretColor(Styles.EditorColor.COLOR_FOREGROUND.getColor());
setBackground(Styles.EditorColor.COLOR_BACKGROUND.getColor());
}
@Override
public void setFont(Font font) {
super.setFont(font);
Document3 doc = getOmDocument();
if (doc != null) {
doc.setFont(font);
}
}
/**
* Return OmDocument instead just a Document. If editor was not initialized
* with OmDocument, it will contains other Document implementation. In this
* case we don't need it.
*/
public Document3 getOmDocument() {
try {
return (Document3) getDocument();
} catch (ClassCastException ex) {
return null;
}
}
/**
* Return true if the specified position is within the active translation
* @param position
* @return
*/
public boolean isInActiveTranslation(int position) {
return (position >= getOmDocument().getTranslationStart()
&& position <= getOmDocument().getTranslationEnd());
}
protected final transient MouseListener mouseListener = new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
autoCompleter.setVisible(false);
// Handle double-click
if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2) {
int mousepos = viewToModel(e.getPoint());
boolean changed = controller.goToSegmentAtLocation(getCaretPosition());
if (!changed) {
if (selectTag(mousepos)) {
e.consume();
}
}
}
}
@Override
public void mousePressed(MouseEvent e) {
if (e.isPopupTrigger()) {
doPopup(e.getPoint());
}
};
@Override
public void mouseReleased(MouseEvent e) {
if (e.isPopupTrigger()) {
doPopup(e.getPoint());
}
}
private void doPopup(Point p) {
int mousepos = viewToModel(p);
JPopupMenu popup = makePopupMenu(mousepos);
if (popup.getComponentCount() > 0) {
popup.show(EditorTextArea3.this, p.x, p.y);
}
}
};
private JPopupMenu makePopupMenu(int pos) {
PopupMenuConstructorInfo[] cons;
synchronized (popupConstructors) {
/**
* Copy constructors - for disable blocking in the procesing
* time.
*/
cons = popupConstructors.toArray(new PopupMenuConstructorInfo[popupConstructors.size()]);
}
boolean isInActiveEntry;
int ae = controller.displayedEntryIndex;
SegmentBuilder sb = controller.m_docSegList[ae];
if (sb.isActive()) {
isInActiveEntry = pos >= sb.getStartPosition() && pos <= sb.getEndPosition();
} else {
isInActiveEntry = false;
}
JPopupMenu popup = new JPopupMenu();
for (PopupMenuConstructorInfo c : cons) {
// call each constructor
c.constructor.addItems(popup, EditorTextArea3.this, pos, isInActiveEntry,
isInActiveTranslation(pos), sb);
}
DockingUI.removeUnusedMenuSeparators(popup);
return popup;
}
/**
* Add new constructor into list and sort full list by priority.
*/
protected void registerPopupMenuConstructors(int priority, IPopupMenuConstructor constructor) {
synchronized (popupConstructors) {
popupConstructors.add(new PopupMenuConstructorInfo(priority, constructor));
Collections.sort(popupConstructors, (o1, o2) -> o1.priority - o2.priority);
}
}
/**
* Redefine some keys behavior. We can't use key listeners, because we have
* to make something AFTER standard keys processing.
*/
@Override
protected void processKeyEvent(KeyEvent e) {
int keyEvent = e.getID();
if (keyEvent == KeyEvent.KEY_RELEASED) {
// key released
super.processKeyEvent(e);
return;
} else if (keyEvent == KeyEvent.KEY_TYPED) {
//key typed
super.processKeyEvent(e);
return;
}
boolean processed = false;
Document3 doc = getOmDocument();
KeyStroke s = KeyStroke.getKeyStrokeForEvent(e);
// non-standard processing
if (autoCompleter.processKeys(e)) {
// The AutoCompleter needs special treatment.
processed = true;
} else if (s.equals(KEYSTROKE_CONTEXT_MENU)) {
// Context Menu key for contextual (right-click) menu (Shift+Esc on Mac)
JPopupMenu popup = makePopupMenu(getCaretPosition());
if (popup.getComponentCount() > 0) {
popup.show(EditorTextArea3.this,
(int) getCaret().getMagicCaretPosition().getX(),
(int) getCaret().getMagicCaretPosition().getY());
processed = true;
}
} else if (s.equals(KEYSTROKE_NEXT)) {
// Advance when 'Use TAB to advance'
if (controller.settings.isUseTabForAdvance()) {
controller.nextEntry();
processed = true;
}
} else if (s.equals(KEYSTROKE_PREV)) {
// Go back when 'Use TAB to advance'
if (controller.settings.isUseTabForAdvance()) {
controller.prevEntry();
processed = true;
}
} else if (s.equals(KEYSTROKE_NEXT_NOT_TAB)) {
// Advance when not 'Use TAB to advance'
if (!controller.settings.isUseTabForAdvance()) {
controller.nextEntry();
processed = true;
} else {
Core.getMainWindow().showTimedStatusMessageRB("ETA_WARNING_TAB_ADVANCE");
processed = true;
}
} else if (s.equals(KEYSTROKE_PREV_NOT_TAB)) {
// Go back when not 'Use TAB to advance'
if (!controller.settings.isUseTabForAdvance()) {
controller.prevEntry();
processed = true;
}
} else if (s.equals(KEYSTROKE_INSERT_LF)) {
// Insert LF
KeyEvent ke = new KeyEvent(e.getComponent(), e.getID(), e.getWhen(), 0, KeyEvent.VK_ENTER, '\n');
super.processKeyEvent(ke);
processed = true;
} else if (s.equals(KEYSTROKE_SELECT_ALL)) {
// Select all
setSelectionStart(doc.getTranslationStart());
setSelectionEnd(doc.getTranslationEnd());
processed = true;
} else if (s.equals(KEYSTROKE_SWITCH_ORIENTATION)) {
// handle Ctrl+Shift+O - toggle orientation LTR-RTL
Cursor oldCursor = this.getCursor();
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
controller.toggleOrientation();
setCursor(oldCursor);
Core.getMainWindow().showTimedStatusMessageRB("ETA_INFO_TOGGLE_LTR_RTL",
StaticUIUtils.getKeyStrokeText(KEYSTROKE_SWITCH_ORIENTATION));
processed = true;
} else if (s.equals(KEYSTROKE_DELETE_PREV_TOKEN)) {
// Delete previous token
try {
processed = wholeTagDelete(false);
if (!processed) {
int offset = getCaretPosition();
int prevWord = Utilities.getPreviousWord(this, offset);
int c = Math.max(prevWord, doc.getTranslationStart());
setSelectionStart(c);
setSelectionEnd(offset);
replaceSelection("");
processed = true;
}
} catch (BadLocationException ex) {
// do nothing
}
} else if (s.equals(KEYSTROKE_DELETE_NEXT_TOKEN)) {
// Delete next token
try {
processed = wholeTagDelete(true);
if (!processed) {
int offset = getCaretPosition();
int nextWord = Utilities.getNextWord(this, offset);
int c = Math.min(nextWord, doc.getTranslationEnd());
setSelectionStart(offset);
setSelectionEnd(c);
replaceSelection("");
processed = true;
}
} catch (BadLocationException ex) {
// do nothing
}
} else if (s.equals(KEYSTROKE_FIRST_SEG)) {
// Jump to beginning of document
int segNum = controller.m_docSegList[0].segmentNumberInProject;
controller.gotoEntry(segNum);
processed = true;
} else if (s.equals(KEYSTROKE_LAST_SEG)) {
// Jump to end of document
int lastSegIndex = controller.m_docSegList.length - 1;
int segNum = controller.m_docSegList[lastSegIndex].segmentNumberInProject;
controller.gotoEntry(segNum);
processed = true;
} else if (s.equals(KEYSTROKE_SKIP_PREV_TOKEN)) {
// Skip over previous token
processed = moveCursorOverTag(false, false);
} else if (s.equals(KEYSTROKE_SKIP_PREV_TOKEN_SEL)) {
// Skip over previous token while extending selection
processed = moveCursorOverTag(true, false);
} else if (s.equals(KEYSTROKE_SKIP_NEXT_TOKEN)) {
// Skip over next token
processed = moveCursorOverTag(false, true);
} else if (s.equals(KEYSTROKE_SKIP_NEXT_TOKEN_SEL)) {
// Skip over next token while extending selection
processed = moveCursorOverTag(true, true);
} else if (s.equals(KEYSTROKE_TOGGLE_CURSOR_LOCK)) {
boolean lockEnabled = !lockCursorToInputArea;
final String key = lockEnabled ? "MW_STATUS_CURSOR_LOCK_ON" : "MW_STATUS_CURSOR_LOCK_OFF";
Core.getMainWindow().showStatusMessageRB(key);
lockCursorToInputArea = lockEnabled;
}
// leave standard processing if need
if (processed) {
e.consume();
} else {
if ((e.getModifiers() & (KeyEvent.CTRL_MASK | KeyEvent.META_MASK | KeyEvent.ALT_MASK)) == 0) {
// there is no Alt,Ctrl,Cmd keys, i.e. it's char
if (e.getKeyCode() != KeyEvent.VK_SHIFT && !isNavigationKey(e.getKeyCode())) {
// it's not a single 'shift' press or navigation key
// fix caret position prior to inserting character
checkAndFixCaret(true);
}
}
super.processKeyEvent(e);
//note that the translation start/end position are not updated yet. This has been updated when then keyreleased event occurs.
}
// some after-processing catches
if (!processed && e.getKeyChar() != 0 && isNavigationKey(e.getKeyCode())) {
//if caret is moved over existing chars, check and fix caret position
checkAndFixCaret(false); //works only in after-processing if translation length (start and end position) has not changed, because start and end position are not updated yet.
autoCompleter.updatePopup(true);
}
}
private boolean isNavigationKey(int keycode) {
switch (keycode) {
// if caret is moved over existing chars, check and fix caret position
case KeyEvent.VK_HOME:
case KeyEvent.VK_END:
case KeyEvent.VK_LEFT:
case KeyEvent.VK_RIGHT:
case KeyEvent.VK_UP:
case KeyEvent.VK_DOWN:
case KeyEvent.VK_KP_LEFT:
case KeyEvent.VK_KP_RIGHT:
case KeyEvent.VK_KP_UP:
case KeyEvent.VK_KP_DOWN:
return true;
}
return false;
}
/**
* Move cursor over tag(possible, with selection)
*
* @param withShift
* true if selection need
* @param checkTagStart
* true if check tag start, false if check tag end
* @return true if tag processed
*/
boolean moveCursorOverTag(boolean withShift, boolean checkTagStart) {
Document3 doc = getOmDocument();
int caret = getCaretPosition();
int start = doc.getTranslationStart();
int end = doc.getTranslationEnd();
if (caret < start || caret > end) {
// We are outside the translation (maybe cursor lock is off).
// Don't try to jump over tags.
return false;
}
if ((caret == start && !checkTagStart) || (caret == end && checkTagStart)) {
// We are at the edge of the translation but moving toward the outside.
// Don't try to jump over tags.
return false;
}
SourceTextEntry ste = doc.controller.getCurrentEntry();
String text = doc.extractTranslation();
int off = caret - start;
// iterate by 'protected parts'
if (ste != null) {
for (ProtectedPart pp : ste.getProtectedParts()) {
if (checkTagStart) {
if (StringUtil.isSubstringAfter(text, off, pp.getTextInSourceSegment())) {
int pos = off + start + pp.getTextInSourceSegment().length();
if (withShift) {
getCaret().moveDot(pos);
} else {
getCaret().setDot(pos);
}
return true;
}
} else {
if (StringUtil.isSubstringBefore(text, off, pp.getTextInSourceSegment())) {
int pos = off + start - pp.getTextInSourceSegment().length();
if (withShift) {
getCaret().moveDot(pos);
} else {
getCaret().setDot(pos);
}
return true;
}
}
}
}
return false;
}
/**
* Whole tag delete before or after cursor
*
* @param checkTagStart
* true if check tag start, false if check tag end
* @return true if tag deleted
*/
boolean wholeTagDelete(boolean checkTagStart) throws BadLocationException {
Document3 doc = getOmDocument();
SourceTextEntry ste = doc.controller.getCurrentEntry();
String text = doc.extractTranslation();
int off = getCaretPosition() - doc.getTranslationStart();
// iterate by 'protected parts'
if (ste != null) {
for (ProtectedPart pp : ste.getProtectedParts()) {
if (checkTagStart) {
if (StringUtil.isSubstringAfter(text, off, pp.getTextInSourceSegment())) {
int pos = off + doc.getTranslationStart();
doc.remove(pos, pp.getTextInSourceSegment().length());
return true;
}
} else {
if (StringUtil.isSubstringBefore(text, off, pp.getTextInSourceSegment())) {
int pos = off + doc.getTranslationStart() - pp.getTextInSourceSegment().length();
doc.remove(pos, pp.getTextInSourceSegment().length());
return true;
}
}
}
}
return false;
}
/**
* Try to select full tag on specified position, in the source and
* translation part of segment.
*
* @param pos
* position
* @return true if selected
*/
boolean selectTag(int pos) {
int s = controller.getSegmentIndexAtLocation(pos);
if (s < 0) {
return false;
}
SegmentBuilder segment = controller.m_docSegList[s];
if (pos < segment.getStartPosition() || pos >= segment.getEndPosition()) {
return false;
}
SourceTextEntry ste = getOmDocument().controller.getCurrentEntry();
if (ste != null) {
try {
String text = getOmDocument().getText(segment.getStartPosition(),
segment.getEndPosition() - segment.getStartPosition());
int off = pos - segment.getStartPosition();
if (off < 0 || off >= text.length()) {
return false;
}
for (ProtectedPart pp : ste.getProtectedParts()) {
int p = -1;
while ((p = text.indexOf(pp.getTextInSourceSegment(), p + 1)) >= 0) {
if (p <= off && off < p + pp.getTextInSourceSegment().length()) {
p += segment.getStartPosition();
select(p, p + pp.getTextInSourceSegment().length());
return true;
}
}
}
} catch (BadLocationException ex) {
}
}
return false;
}
/**
* Checks whether the selection & caret is inside editable text, and changes
* their positions accordingly if not. Convenience method for
* {@link #checkAndFixCaret(boolean)} that always forcibly fixes the caret.
*/
void checkAndFixCaret() {
checkAndFixCaret(true);
}
/**
* Checks whether the selection & caret is inside editable text, and changes
* their positions accordingly if not.
*
* @param force
* When true, ignore {@link #lockCursorToInputArea} and always
* fix the caret even if the user has enabled free roaming
*/
void checkAndFixCaret(boolean force) {
if (!force && !lockCursorToInputArea) {
return;
}
Document3 doc = getOmDocument();
if (doc == null) {
// doc is not active
return;
}
if (!doc.isEditMode()) {
return;
}
// int pos = m_editor.getCaretPosition();
int spos = getSelectionStart();
int epos = getSelectionEnd();
int start = doc.getTranslationStart();
int end = doc.getTranslationEnd();
if (spos != epos) {
// dealing with a selection here - make sure it's w/in bounds
if (spos < start) {
fixSelectionStart(start);
} else if (spos > end) {
fixSelectionStart(end);
}
if (epos > end) {
fixSelectionEnd(end);
} else if (epos < start) {
fixSelectionStart(start);
}
} else {
// non selected text
if (spos < start) {
setCaretPosition(start);
} else if (spos > end) {
setCaretPosition(end);
}
}
}
/**
* Need to use own implementation, because standard method moves caret at
* the end.
*/
private void fixSelectionStart(int start) {
if (getCaretPosition() <= start) {
// caret at the left - mark from ent to start
setCaretPosition(getSelectionEnd());
moveCaretPosition(start);
} else {
setSelectionStart(start);
}
}
/**
* Need to use own implementation, because standard method moves caret at
* the end.
*/
private void fixSelectionEnd(int end) {
setSelectionEnd(end);
}
/**
* Allow to paste into segment, even selection outside editable segment. In
* this case selection will be truncated into segment's boundaries.
*/
@Override
public void paste() {
checkAndFixCaret();
super.paste();
}
/**
* Allow to cut segment, even selection outside editable segment. In this
* case selection will be truncated into segment's boundaries.
*/
@Override
public void cut() {
checkAndFixCaret();
super.cut();
}
/**
* Remove invisible direction chars on the copy text into clipboard.
*/
@Override
public String getSelectedText() {
String st = super.getSelectedText();
return st != null ? EditorUtils.removeDirectionChars(st) : null;
}
@Override
public String getToolTipText(MouseEvent event) {
int pos = viewToModel(event.getPoint());
int s = controller.getSegmentIndexAtLocation(pos);
return controller.markerController.getToolTips(s, pos);
}
/**
* Factory for create own view.
*/
public static final ViewFactory factory3 = new ViewFactory() {
public View create(Element elem) {
String kind = elem.getName();
if (kind != null) {
if (kind.equals(AbstractDocument.ContentElementName)) {
return new ViewLabel(elem);
} else if (kind.equals(AbstractDocument.ParagraphElementName)) {
return new ViewParagraph(elem);
} else if (kind.equals(AbstractDocument.SectionElementName)) {
return new BoxView(elem, View.Y_AXIS);
} else if (kind.equals(StyleConstants.ComponentElementName)) {
return new ComponentView(elem);
} else if (kind.equals(StyleConstants.IconElementName)) {
return new IconView(elem);
}
}
// default to text display
return new ViewLabel(elem);
}
};
private static class PopupMenuConstructorInfo {
final int priority;
final IPopupMenuConstructor constructor;
public PopupMenuConstructorInfo(int priority, IPopupMenuConstructor constructor) {
this.priority = priority;
this.constructor = constructor;
}
}
}