/************************************************************************** OmegaT - Computer Assisted Translation (CAT) tool with fuzzy matching, translation memory, keyword search, glossaries, and translation leveraging into updated projects. Copyright (C) 2013 Zoltan Bartko, Aaron Madlon-Kay 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.glossary; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Locale; import org.omegat.core.Core; import org.omegat.gui.editor.autocompleter.AutoCompleter; import org.omegat.gui.editor.autocompleter.AutoCompleterItem; import org.omegat.gui.editor.autocompleter.AutoCompleterListView; import org.omegat.util.OStrings; import org.omegat.util.Preferences; import org.omegat.util.StringUtil; /** * The glossary auto-completer view. * * @author Zoltan Bartko <bartkozoltan@bartkozoltan.com> * @author Aaron Madlon-Kay */ public class GlossaryAutoCompleterView extends AutoCompleterListView { public GlossaryAutoCompleterView() { super(OStrings.getString("AC_GLOSSARY_VIEW")); } /* Users with gigantic glossaries can get too many popups, so adjust the behavior here. * Only pop up if a) we have suggestions, and b) if there's more than one page of * suggestions then the user should have input at least 2 characters. */ @Override public boolean shouldPopUp() { String leadingText = getLeadingText(); List<AutoCompleterItem> entries = computeListData(leadingText, true); return !entries.isEmpty() && (leadingText.codePointCount(0, leadingText.length()) > 1 || entries.size() <= AutoCompleter.PAGE_ROW_COUNT); } @Override public List<AutoCompleterItem> computeListData(String prevText, boolean contextualOnly) { String wordChunk = getLastToken(prevText); String sortMatchTo = wordChunk; List<AutoCompleterItem> result = new ArrayList<AutoCompleterItem>(); List<GlossaryEntry> entries = Core.getGlossary().getDisplayedEntries(); // Get contextual results fillMatchingTerms(result, entries, wordChunk); if (result.isEmpty() && !contextualOnly) { // Get non-contextual results only if called for fillMatchingTerms(result, entries, null); sortMatchTo = null; } Collections.sort(result, new GlossaryComparator(entries, sortMatchTo)); return result; } /** * Fill provided result list with AutCompleterItems matching the provided wordChunk. * If the wordChunk is null, all available items will be added. However if the wordChunk is * empty ("") then no items will be added. * @param result * @param glossary * @param context */ private void fillMatchingTerms(List<AutoCompleterItem> result, List<GlossaryEntry> glossary, String context) { if ("".equals(context)) { // Context is present but empty--we consider no terms to match. return; } for (GlossaryEntry entry : glossary) { for (String term : entry.getLocTerms(true)) { if (!termMatchesChunk(term, context)) { continue; } int length = context == null ? 0 : context.length(); // Add matched-capitalization version String payload = StringUtil.matchCapitalization(term, context, getTargetLocale()); AutoCompleterItem item = new AutoCompleterItem(payload, new String[] { entry.getSrcText() }, length); if (!result.contains(item)) { result.add(item); } // Add original, if it differs if (!payload.equals(term)) { result.add(new AutoCompleterItem(term, new String[] { entry.getSrcText() }, length)); } } } } private boolean termMatchesChunk(String term, String context) { if (context == null) { // Consider null context to match everything return true; } Locale locale = getTargetLocale(); String lowerTerm = term.toLowerCase(locale); String lowerContext = context.toLowerCase(locale); // Consider a term to NOT match if it is the same (modulo case) as the context (i.e. it is already present) return !lowerTerm.equals(lowerContext) && lowerTerm.startsWith(lowerContext); } private Locale getTargetLocale() { return getTargetLanguage().getLocale(); } @Override public String itemToString(AutoCompleterItem item) { if (Preferences.isPreference(Preferences.AC_GLOSSARY_SHOW_SOURCE) && item.extras != null) { if (Preferences.isPreference(Preferences.AC_GLOSSARY_SHOW_TARGET_BEFORE_SOURCE)) { return item.payload + " \u2190 " + item.extras[0]; } else { return item.extras[0] + " \u2192 " + item.payload; } } else { return item.payload; } } static class GlossaryComparator implements Comparator<AutoCompleterItem> { private boolean bySource = Preferences.isPreference(Preferences.AC_GLOSSARY_SORT_BY_SOURCE); private boolean byLength = Preferences.isPreference(Preferences.AC_GLOSSARY_SORT_BY_LENGTH); private boolean alphabetically = Preferences.isPreference(Preferences.AC_GLOSSARY_SORT_ALPHABETICALLY); private final List<GlossaryEntry> entries; private final String matchTo; public GlossaryComparator(List<GlossaryEntry> entries, String matchTo) { this.entries = entries; this.matchTo = matchTo; } @Override public int compare(AutoCompleterItem o1, AutoCompleterItem o2) { // If one of the payloads starts with the exact matchTo string, prioritize that one. if (!StringUtil.isEmpty(matchTo)) { boolean o1Matches = o1.payload.startsWith(matchTo); boolean o2Matches = o2.payload.startsWith(matchTo); if (o1Matches && !o2Matches) { return -1; } if (!o1Matches && o2Matches) { return 1; } boolean o1IsOriginal = isOriginalEntry(o1); boolean o2IsOriginal = isOriginalEntry(o2); if (o1IsOriginal && !o2IsOriginal) { return -1; } else if (!o1IsOriginal && o2IsOriginal) { return 1; } } // Sort alphabetically by source term if (bySource) { int result = o1.extras[0].compareTo(o2.extras[0]); if (result != 0) { return result; } } // Sorting for same source with multiple targets if (o1.extras[0].equals(o2.extras[0])) { if (byLength) { if (o1.payload.length() < o2.payload.length()) { return 1; } else if (o1.payload.length() > o2.payload.length()) { return -1; } } if (alphabetically) { return o1.payload.compareTo(o2.payload); } } // If we make it here, we should ensure the sorting is the same // as in the original list of entries. int i1 = -1; int i2 = -1; for (int i = 0; i < entries.size(); i++) { if (entries.get(i).getSrcText().equals(o1.extras[0])) { i1 = i; } if (entries.get(i).getSrcText().equals(o2.extras[0])) { i2 = i; } if (i1 != -1 && i2 != -1) { break; } } if (i1 < i2) { return -1; } else if (i1 > i2) { return 1; } return 0; } private boolean isOriginalEntry(AutoCompleterItem item) { for (GlossaryEntry entry : entries) { for (String term : entry.getLocTerms(true)) { if (item.payload.equals(term)) { return true; } } } return false; } } @Override protected boolean isEnabled() { return Preferences.isPreferenceDefault(Preferences.AC_GLOSSARY_ENABLED, true); } }