/**************************************************************************
OmegaT - Computer Assisted Translation (CAT) tool
with fuzzy matching, translation memory, keyword search,
glossaries, and translation leveraging into updated projects.
Copyright (C) 2009 Didier Briel
2010 Wildrich Fourie
2011 Alex Buloichik, Didier Briel
2015 Aaron Madlon-Kay
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.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.Transferable;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.List;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.text.AbstractDocument;
import javax.swing.text.BadLocationException;
import javax.swing.text.JTextComponent;
import org.omegat.core.Core;
import org.omegat.core.data.SourceTextEntry;
import org.omegat.core.spellchecker.SpellCheckerMarker;
import org.omegat.tokenizer.ITokenizer.StemmingMode;
import org.omegat.util.Log;
import org.omegat.util.OStrings;
import org.omegat.util.StringUtil;
import org.omegat.util.TagUtil;
import org.omegat.util.TagUtil.Tag;
import org.omegat.util.Token;
import org.omegat.util.gui.MenuItemPager;
import org.omegat.util.gui.UIThreadsUtil;
/**
* Some standard editor popups.
*
* @author Alex Buloichik (alex73mail@gmail.com)
* @author Wildrich Fourie
* @author Didier Briel
* @author Aaron Madlon-Kay
*/
public final class EditorPopups {
public static void init(EditorController ec) {
ec.registerPopupMenuConstructors(100, new SpellCheckerPopup(ec));
ec.registerPopupMenuConstructors(200, new GoToSegmentPopup(ec));
ec.registerPopupMenuConstructors(400, new DefaultPopup());
ec.registerPopupMenuConstructors(500, new DuplicateSegmentsPopup(ec));
ec.registerPopupMenuConstructors(600, new EmptyNoneTranslationPopup(ec));
ec.registerPopupMenuConstructors(700, new InsertTagsPopup(ec));
ec.registerPopupMenuConstructors(700, new InsertBidiPopup(ec));
}
private EditorPopups() {
}
/**
* create the spell checker popup menu - suggestions for a wrong word, add
* and ignore. Works only for the active segment, for the translation
*
* @param point
* : where should the popup be shown
*/
public static class SpellCheckerPopup implements IPopupMenuConstructor {
protected final EditorController ec;
public SpellCheckerPopup(EditorController ec) {
this.ec = ec;
}
public void addItems(JPopupMenu menu, final JTextComponent comp, int mousepos,
boolean isInActiveEntry, boolean isInActiveTranslation, SegmentBuilder sb) {
if (!ec.getSettings().isAutoSpellChecking()) {
// spellchecker disabled
return;
}
if (!isInActiveTranslation) {
// there is no need to display suggestions
return;
}
// Use the project's target tokenizer to determine the word that was right-clicked.
// EditorUtils.getWordEnd() and getWordStart() use Java's built-in BreakIterator
// under the hood, which leads to inconsistent results when compared to other spell-
// checking functionality in OmegaT.
String translation = ec.getCurrentTranslation();
Token tok = null;
int relOffset = ec.getPositionInEntryTranslation(mousepos);
for (Token t : Core.getProject().getTargetTokenizer().tokenizeWords(translation, StemmingMode.NONE)) {
if (t.getOffset() <= relOffset && relOffset < t.getOffset() + t.getLength()) {
tok = t;
break;
}
}
if (tok == null) {
return;
}
final String word = tok.getTextFromString(translation);
// The wordStart must be the absolute offset in the Editor document.
final int wordStart = mousepos - relOffset + tok.getOffset();
final int wordLength = tok.getLength();
final AbstractDocument xlDoc = (AbstractDocument) comp.getDocument();
if (!Core.getSpellChecker().isCorrect(word)) {
// get the suggestions and create a menu
List<String> suggestions = Core.getSpellChecker().suggest(word);
// the suggestions
for (final String replacement : suggestions) {
JMenuItem item = menu.add(replacement);
item.addActionListener(new ActionListener() {
// the action: replace the word with the selected
// suggestion
public void actionPerformed(ActionEvent e) {
try {
int pos = comp.getCaretPosition();
xlDoc.replace(wordStart, wordLength, replacement, null);
comp.setCaretPosition(pos);
} catch (BadLocationException exc) {
Log.log(exc);
}
}
});
}
// what if no action is done?
if (suggestions.isEmpty()) {
JMenuItem item = menu.add(OStrings.getString("SC_NO_SUGGESTIONS"));
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
// just hide the menu
}
});
}
menu.addSeparator();
// let us ignore it
JMenuItem item = menu.add(OStrings.getString("SC_IGNORE_ALL"));
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
addIgnoreWord(word, wordStart, false);
}
});
// or add it to the dictionary
item = menu.add(OStrings.getString("SC_ADD_TO_DICTIONARY"));
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
addIgnoreWord(word, wordStart, true);
}
});
menu.addSeparator();
}
}
/**
* add a new word to the spell checker or ignore a word
*
* @param word
* : the word in question
* @param offset
* : the offset of the word in the editor
* @param add
* : true for add, false for ignore
*/
protected void addIgnoreWord(final String word, final int offset, final boolean add) {
UIThreadsUtil.mustBeSwingThread();
if (add) {
Core.getSpellChecker().learnWord(word);
} else {
Core.getSpellChecker().ignoreWord(word);
}
ec.remarkOneMarker(SpellCheckerMarker.class.getName());
}
}
public static class DefaultPopup implements IPopupMenuConstructor {
/**
* Creates the Cut, Copy and Paste menu items
*/
public void addItems(JPopupMenu menu, final JTextComponent comp, int mousepos,
boolean isInActiveEntry, boolean isInActiveTranslation, SegmentBuilder sb) {
final String selText = comp.getSelectedText();
final Clipboard omClipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
Transferable contents = omClipboard.getContents(this);
boolean cutEnabled = false;
boolean copyEnabled = false;
boolean pasteEnabled = false;
// Calc enabled/disabled
if (selText != null && comp.getSelectionStart() <= mousepos && mousepos <= comp.getSelectionEnd()) {
// only on selected text
if (isInActiveTranslation) {
// cut only in editable zone
cutEnabled = true;
}
// copy in any place
copyEnabled = true;
}
if (contents != null && isInActiveTranslation) {
pasteEnabled = true;
}
// Cut
JMenuItem cutContextItem = menu.add(OStrings.getString("CCP_CUT"));
if (cutEnabled) {
cutContextItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
comp.cut();
}
});
} else {
cutContextItem.setEnabled(false);
}
// Copy
JMenuItem copyContextItem = menu.add(OStrings.getString("CCP_COPY"));
if (copyEnabled) {
copyContextItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
comp.copy();
}
});
} else {
copyContextItem.setEnabled(false);
}
// Paste
JMenuItem pasteContextItem = menu.add(OStrings.getString("CCP_PASTE"));
if (pasteEnabled) {
pasteContextItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
comp.paste();
}
});
} else {
pasteContextItem.setEnabled(false);
}
menu.addSeparator();
// Add glossary entry
JMenuItem item = menu.add(OStrings.getString("GUI_GLOSSARYWINDOW_addentry"));
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
Core.getGlossary()
.showCreateGlossaryEntryDialog(Core.getMainWindow().getApplicationFrame());
}
});
menu.addSeparator();
}
}
public static class GoToSegmentPopup implements IPopupMenuConstructor {
protected final EditorController ec;
public GoToSegmentPopup(EditorController ec) {
this.ec = ec;
}
/**
* creates a popup menu for inactive segments - with an item allowing to
* go to the given segment.
*/
public void addItems(JPopupMenu menu, final JTextComponent comp, final int mousepos,
boolean isInActiveEntry, boolean isInActiveTranslation, SegmentBuilder sb) {
if (isInActiveEntry) {
return;
}
JMenuItem item = menu.add(OStrings.getString("MW_PROMPT_SEG_NR_TITLE"));
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
comp.setCaretPosition(mousepos);
ec.goToSegmentAtLocation(comp.getCaretPosition());
}
});
menu.addSeparator();
}
}
public static class DuplicateSegmentsPopup implements IPopupMenuConstructor {
protected final EditorController ec;
public DuplicateSegmentsPopup(EditorController ec) {
this.ec = ec;
}
@Override
public void addItems(JPopupMenu menu, JTextComponent comp,
int mousepos, boolean isInActiveEntry,
boolean isInActiveTranslation, SegmentBuilder sb) {
if (!isInActiveEntry) {
return;
}
SourceTextEntry ste = ec.getCurrentEntry();
List<SourceTextEntry> dups = ste.getDuplicates();
if (dups.isEmpty()) {
return;
}
JMenuItem header = menu
.add(StringUtil.format(OStrings.getString("MW_GO_TO_DUPLICATE_HEADER"), dups.size()));
header.setEnabled(false);
MenuItemPager pager = new MenuItemPager(menu);
for (SourceTextEntry entry : dups) {
int entryNum = entry.entryNum();
String label = StringUtil.format(OStrings.getString("MW_GO_TO_DUPLICATE_ITEM"), entryNum);
JMenuItem item = pager.add(new JMenuItem(label));
item.addActionListener(e -> ec.gotoEntry(entryNum));
}
menu.addSeparator();
}
}
public static class EmptyNoneTranslationPopup implements IPopupMenuConstructor {
protected final EditorController ec;
public EmptyNoneTranslationPopup(EditorController ec) {
this.ec = ec;
}
/**
* creates a popup menu to remove translation or set empty translation
*/
public void addItems(JPopupMenu menu, final JTextComponent comp, final int mousepos,
boolean isInActiveEntry, boolean isInActiveTranslation, SegmentBuilder sb) {
if (!isInActiveEntry) {
return;
}
menu.add(OStrings.getString("TRANS_POP_EMPTY_TRANSLATION")).addActionListener(
new ActionListener() {
public void actionPerformed(ActionEvent e) {
ec.registerEmptyTranslation();
}
});
menu.add(OStrings.getString("TRANS_POP_REMOVE_TRANSLATION")).addActionListener(
new ActionListener() {
public void actionPerformed(ActionEvent e) {
ec.registerUntranslated();
}
});
menu.add(OStrings.getString("TRANS_POP_IDENTICAL_TRANSLATION")).addActionListener(
new ActionListener() {
public void actionPerformed(ActionEvent e) {
ec.registerIdenticalTranslation();
}
});
menu.addSeparator();
}
}
public static class InsertTagsPopup implements IPopupMenuConstructor {
protected final EditorController ec;
public InsertTagsPopup(EditorController ec) {
this.ec = ec;
}
public void addItems(JPopupMenu menu, final JTextComponent comp, final int mousepos, boolean isInActiveEntry,
boolean isInActiveTranslation, SegmentBuilder sb) {
if (!isInActiveTranslation) {
return;
}
for (final Tag tag : TagUtil.getAllTagsMissingFromTarget()) {
JMenuItem item = menu.add(StringUtil.format(OStrings.getString("TF_MENU_EDIT_TAG_INSERT_N"), tag.tag));
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
Core.getEditor().insertTag(tag.tag);
}
});
}
menu.addSeparator();
}
}
public static class InsertBidiPopup implements IPopupMenuConstructor {
protected final EditorController ec;
protected String[] names = new String[] { "TF_MENU_EDIT_INSERT_CHARS_LRM",
"TF_MENU_EDIT_INSERT_CHARS_RLM", "TF_MENU_EDIT_INSERT_CHARS_LRE",
"TF_MENU_EDIT_INSERT_CHARS_RLE", "TF_MENU_EDIT_INSERT_CHARS_PDF" };
protected String[] inserts = new String[] { "\u200E", "\u200F", "\u202A", "\u202B", "\u202C" };
public InsertBidiPopup(EditorController ec) {
this.ec = ec;
}
public void addItems(JPopupMenu menu, final JTextComponent comp, final int mousepos,
boolean isInActiveEntry, boolean isInActiveTranslation, SegmentBuilder sb) {
if (!isInActiveTranslation) {
return;
}
JMenu submenu = new JMenu(OStrings.getString("TF_MENU_EDIT_INSERT_CHARS"));
for (int i = 0; i < names.length; i++) {
JMenuItem item = new JMenuItem(OStrings.getString(names[i]));
final String insertText = inserts[i];
item.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Core.getEditor().insertText(insertText);
}
});
submenu.add(item);
}
menu.add(submenu);
}
}
}