/************************************************************************** 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 2014-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.autocompleter; import java.awt.Component; import java.awt.Point; import java.awt.Rectangle; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.List; import javax.swing.DefaultListCellRenderer; import javax.swing.JList; import javax.swing.JScrollBar; import javax.swing.KeyStroke; import javax.swing.border.Border; import javax.swing.border.EmptyBorder; import org.omegat.tokenizer.ITokenizer; import org.omegat.util.OStrings; import org.omegat.util.Token; /** * A list based auto-completer view. * * @author bartkoz * @author Aaron Madlon-Kay */ public abstract class AutoCompleterListView extends AbstractAutoCompleterView { private static JList<AutoCompleterItem> list; private static AutoCompleterItem NO_SUGGESTIONS = new AutoCompleterItem( OStrings.getString("AC_NO_SUGGESTIONS"), null, 0); public AutoCompleterListView(String name) { super(name); getList().setFocusable(false); } public JList<AutoCompleterItem> getList() { if (list == null) { list = new JList<>(); list.setCellRenderer(new CellRenderer()); list.addMouseListener(mouseAdapter); } return list; } private final MouseAdapter mouseAdapter = new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { if (e.getClickCount() == 2) { Point p = e.getPoint(); int i = list.locationToIndex(p); if (list.getSelectedIndex() == i && list.getCellBounds(i, i).contains(p)) { completer.doSelection(); } } } }; @Override public boolean processKeys(KeyEvent e) { int code = e.getKeyCode(); if ((code == KeyEvent.VK_LEFT || code == KeyEvent.VK_RIGHT) && e.getModifiers() == 0 && completer.isVisible() && completer.didPopUpAutomatically) { // Close autocompleter if user presses left or right (we can't use these anyway since it's a // vertical list) and the completer appeared automatically. completer.setVisible(false); // Don't consume here so that the cursor movement can still take place. return false; } KeyStroke s = KeyStroke.getKeyStrokeForEvent(e); AutoCompleterKeys keys = completer.getKeys(); if (s.equals(keys.listUp) || s.equals(keys.listUpEmacs)) { selectPreviousPossibleValue(); return true; } if (s.equals(keys.listDown) || s.equals(keys.listDownEmacs)) { selectNextPossibleValue(); return true; } if (s.equals(keys.listPageUp)) { selectPreviousPossibleValueByPage(); return true; } if (s.equals(keys.listPageDown)) { selectNextPossibleValueByPage(); return true; } return false; } /** * Selects the next item in the list. */ protected void selectNextPossibleValue() { int i = (getList().getSelectedIndex() + 1) % getList().getModel().getSize(); getList().setSelectedIndex(i); getList().ensureIndexIsVisible(i); } /** * Selects the item in the list following the current one by one page, or the last item if * there is less than one page following. */ protected void selectNextPossibleValueByPage() { int page = getList().getLastVisibleIndex() - getList().getFirstVisibleIndex(); int i = Math.min(getList().getSelectedIndex() + page, getList().getModel().getSize() - 1); getList().setSelectedIndex(i); getList().ensureIndexIsVisible(i); } /** * Selects the previous item in the list. */ protected void selectPreviousPossibleValue() { int size = getList().getModel().getSize(); int i = (getList().getSelectedIndex() - 1 + size) % size; getList().setSelectedIndex(i); getList().ensureIndexIsVisible(i); } /** * Selects the item in the list preceding the current one by one page, or the first item if * there is less than one page preceding. */ protected void selectPreviousPossibleValueByPage() { int page = getList().getLastVisibleIndex() - getList().getFirstVisibleIndex(); int i = Math.max(getList().getSelectedIndex() - page, 0); getList().setSelectedIndex(i); getList().ensureIndexIsVisible(i); } @Override public int getRowCount() { return getList().getModel().getSize(); } @Override public int getPreferredHeight() { Rectangle bounds = getList().getCellBounds(0, 0); return (int) (getModifiedRowCount() * (bounds == null ? getList().getFont().getSize() : bounds.getHeight())); } @Override public int getPreferredWidth() { int width = getList().getPreferredSize().width; JScrollBar bar = completer.scroll.getVerticalScrollBar(); if (bar != null) { width += bar.getPreferredSize().width; } return width; }; protected void setData(AutoCompleterItem... entries) { getList().setListData(entries); if (entries.length > 0) { getList().setSelectedIndex(0); getList().invalidate(); getList().scrollRectToVisible(new Rectangle()); } } @Override public AutoCompleterItem getSelectedValue() { Object item = getList().getSelectedValue(); return item == NO_SUGGESTIONS ? null : (AutoCompleterItem) item; } @Override public Component getViewContent() { getList().setVisibleRowCount(getModifiedRowCount()); return getList(); } @Override public void updateViewData() { List<AutoCompleterItem> entryList = computeListData(getLeadingText(), false); if (entryList.isEmpty()) { setData(NO_SUGGESTIONS); } else { setData(entryList.toArray(new AutoCompleterItem[entryList.size()])); } } @Override public boolean shouldPopUp() { return !computeListData(getLeadingText(), true).isEmpty(); } protected String getLastToken(String text) { String token = ""; ITokenizer tokenizer = getTokenizer(); Token[] tokens = tokenizer.tokenizeVerbatim(text); if (tokens.length != 0) { Token lastToken = tokens[tokens.length - 1]; String lastString = text.substring(lastToken.getOffset()).trim(); if (!lastString.isEmpty()) { token = lastString; } } return token; } /** * Compute the items visible in the auto-completer list * @param prevText the text in the editing field up to the cursor location * @return a list of AutoCompleterItems. */ public abstract List<AutoCompleterItem> computeListData(String prevText, boolean contextualOnly); /** * Each view should determine how to print a view item. * @param item The item to print * @return A string representation of the view item */ public abstract String itemToString(AutoCompleterItem item); private static final Border LIST_MARGIN_BORDER = new EmptyBorder(0, 5, 0, 5); @SuppressWarnings("serial") private class CellRenderer extends DefaultListCellRenderer { @Override public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) { setBorder(LIST_MARGIN_BORDER); if (value == NO_SUGGESTIONS) { setText(((AutoCompleterItem)value).payload); } else { AutoCompleterListView aclView = (AutoCompleterListView) completer.getCurrentView(); AutoCompleterItem acItem = (AutoCompleterItem) value; setText(aclView.itemToString(acItem)); } if (isSelected) { setBackground(list.getSelectionBackground()); setForeground(list.getSelectionForeground()); } else { setBackground(list.getBackground()); setForeground(list.getForeground()); } return this; } } }