/************************************************************************** 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 2012 Jean-Christophe Helary 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.dictionaries; import java.awt.Dimension; import java.awt.Font; import java.awt.Point; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; import javax.swing.JCheckBoxMenuItem; import javax.swing.JMenuItem; import javax.swing.JPopupMenu; import javax.swing.text.BadLocationException; import javax.swing.text.Document; import javax.swing.text.Element; import javax.swing.text.html.HTMLDocument; import javax.swing.text.html.StyleSheet; import org.omegat.core.Core; import org.omegat.core.CoreEvents; import org.omegat.core.data.IProject; import org.omegat.core.data.SourceTextEntry; import org.omegat.core.dictionaries.DictionariesManager; import org.omegat.core.dictionaries.DictionaryEntry; import org.omegat.core.dictionaries.IDictionaryFactory; import org.omegat.core.events.IEditorEventListener; import org.omegat.gui.common.EntryInfoSearchThread; import org.omegat.gui.common.EntryInfoThreadPane; import org.omegat.gui.main.DockableScrollPane; import org.omegat.gui.main.IMainWindow; import org.omegat.tokenizer.ITokenizer; import org.omegat.tokenizer.ITokenizer.StemmingMode; import org.omegat.util.OStrings; import org.omegat.util.Preferences; import org.omegat.util.StringUtil; import org.omegat.util.gui.IPaneMenu; import org.omegat.util.gui.StaticUIUtils; import org.omegat.util.gui.Styles.EditorColor; import org.omegat.util.gui.UIThreadsUtil; /** * This is a Dictionaries pane that displays dictionaries entries. * * @author Alex Buloichik <alex73mail@gmail.com> * @author Jean-Christophe Helary * @author Aaron Madlon-Kay */ @SuppressWarnings("serial") public class DictionariesTextArea extends EntryInfoThreadPane<List<DictionaryEntry>> implements IDictionaries, IPaneMenu { private static final String EXPLANATION = OStrings.getString("GUI_DICTIONARYWINDOW_explanation"); protected final DictionariesManager manager = new DictionariesManager(this); protected final List<String> displayedWords = new ArrayList<String>(); protected ITokenizer tokenizer; private final DockableScrollPane scrollPane; public DictionariesTextArea(IMainWindow mw) { super(true); setContentType("text/html"); ((HTMLDocument) getDocument()).setPreservesUnknownTags(false); setFont(getFont()); String title = OStrings.getString("GUI_MATCHWINDOW_SUBWINDOWTITLE_Dictionary"); scrollPane = new DockableScrollPane("DICTIONARY", title, this, true); mw.addDockable(scrollPane); addMouseListener(mouseCallback); setEditable(false); StaticUIUtils.makeCaretAlwaysVisible(this); setText(EXPLANATION); setMinimumSize(new Dimension(100, 50)); CoreEvents.registerEditorEventListener(new IEditorEventListener() { public void onNewWord(String newWord) { callDictionary(newWord); } }); Preferences.addPropertyChangeListener(Preferences.DICTIONARY_FUZZY_MATCHING, e -> refresh()); } @Override public void setFont(Font font) { super.setFont(font); Document doc = getDocument(); if (!(doc instanceof HTMLDocument)) { return; } StyleSheet styleSheet = ((HTMLDocument) doc).getStyleSheet(); styleSheet.addRule("body { font-family: " + font.getName() + "; " + " font-size: " + font.getSize() + "; " + " font-style: " + (font.getStyle() == Font.BOLD ? "bold" : font.getStyle() == Font.ITALIC ? "italic" : "normal") + "; " + " color: " + EditorColor.COLOR_FOREGROUND.toHex() + "; " + " background: " + EditorColor.COLOR_BACKGROUND.toHex() + "; " + " }"); } @Override protected void onProjectOpen() { clear(); IProject project = Core.getProject(); tokenizer = project.getSourceTokenizer(); manager.setIndexLanguage(project.getProjectProperties().getSourceLanguage()); manager.setTokenizer(tokenizer); manager.start(new File(project.getProjectProperties().getDictRoot())); } @Override protected void onProjectClose() { clear(); setText(EXPLANATION); manager.stop(); tokenizer = null; } /** Clears up the pane. */ @Override public void clear() { super.clear(); displayedWords.clear(); } /** * Move position in pane to the currently selected word. */ protected void callDictionary(String word) { UIThreadsUtil.mustBeSwingThread(); HTMLDocument doc = (HTMLDocument) getDocument(); int i = displayedWords.indexOf(word.toLowerCase()); if (i == -1) { return; } Element el = doc.getElement(Integer.toString(i)); if (el == null) { return; } try { // rectangle to be visible Rectangle rect = modelToView(el.getStartOffset()); // show 2 lines if (rect != null) { rect.height *= 2; scrollRectToVisible(rect); } } catch (BadLocationException ex) { // shouldn't be throwed } } @Override public void onEntryActivated(SourceTextEntry newEntry) { scrollPane.stopNotifying(); super.onEntryActivated(newEntry); } @Override protected void startSearchThread(SourceTextEntry newEntry) { new DictionaryEntriesSearchThread(newEntry).start(); } /** * Refresh content on dictionary file changed. */ public void refresh() { SourceTextEntry ste = Core.getEditor().getCurrentEntry(); if (ste != null) { startSearchThread(ste); } } @Override protected void setFoundResult(final SourceTextEntry se, final List<DictionaryEntry> data) { UIThreadsUtil.mustBeSwingThread(); clear(); if (data == null) { return; } if (!data.isEmpty() && Preferences.isPreference(Preferences.NOTIFY_DICTIONARY_HITS)) { scrollPane.notify(true); } StringBuilder txt = new StringBuilder(); boolean wasPrev = false; int i = 0; for (DictionaryEntry de : data) { if (wasPrev) { txt.append("<br><hr>"); } else { wasPrev = true; } txt.append("<b><span id=\"" + i + "\">"); txt.append(de.getWord()); txt.append("</span></b>"); txt.append(" - ").append(de.getArticle()); displayedWords.add(de.getWord().toLowerCase()); i++; } setText(txt.toString()); } protected final transient MouseAdapter mouseCallback = new MouseAdapter() { @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) { UIThreadsUtil.mustBeSwingThread(); JPopupMenu popup = new JPopupMenu(); int mousepos = viewToModel(p); final String word = getWordAtOffset(mousepos); if (word != null) { JMenuItem item = popup.add(StringUtil.format(OStrings.getString("DICTIONARY_HIDE"), word)); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { manager.addIgnoreWord(word); }; }); popup.show(DictionariesTextArea.this, p.x, p.y); } } }; private String getWordAtOffset(int offset) { HTMLDocument doc = (HTMLDocument) getDocument(); for (int i = 0; i < displayedWords.size(); i++) { Element el = doc.getElement(Integer.toString(i)); if (el == null) { continue; } if (el.getStartOffset() <= offset && el.getEndOffset() >= offset) { return displayedWords.get(i); } } return null; } /** * Thread for search data in dictionaries. */ public class DictionaryEntriesSearchThread extends EntryInfoSearchThread<List<DictionaryEntry>> { protected final String src; protected final ITokenizer tok; public DictionaryEntriesSearchThread(final SourceTextEntry newEntry) { super(DictionariesTextArea.this, newEntry); src = newEntry.getSrcText(); tok = tokenizer; } @Override protected List<DictionaryEntry> search() { if (tok == null) { return null; } // Just get the words. Stemming and other complex lookup logic is // left to DictionaryManager. List<String> words = Stream.of(tok.tokenizeWordsToStrings(src, StemmingMode.NONE)).distinct() .collect(Collectors.toList()); List<DictionaryEntry> result = manager.findWords(words); Collections.sort(result); return result; } } @Override public void addDictionaryFactory(IDictionaryFactory factory) { manager.addDictionaryFactory(factory); } @Override public void removeDictionaryFactory(IDictionaryFactory factory) { manager.removeDictionaryFactory(factory); } @Override public void populatePaneMenu(JPopupMenu menu) { final JMenuItem notify = new JCheckBoxMenuItem( OStrings.getString("GUI_DICTIONARYWINDOW_SETTINGS_NOTIFICATIONS")); notify.setSelected(Preferences.isPreference(Preferences.NOTIFY_DICTIONARY_HITS)); notify.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { Preferences.setPreference(Preferences.NOTIFY_DICTIONARY_HITS, notify.isSelected()); } }); menu.add(notify); } }