+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ *
+ * This file is part of Jalview.
+ *
+ * Jalview 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.
+ *
+ * Jalview 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 Jalview. If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
package jalview.io.cache;
+import jalview.bin.Cache;
import jalview.util.MessageManager;
+import jalview.util.Platform;
-import java.awt.Color;
-import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
+import java.awt.event.FocusListener;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import javax.swing.JComboBox;
-import javax.swing.JLabel;
+import javax.swing.JComponent;
import javax.swing.JMenuItem;
-import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
-import javax.swing.text.AttributeSet;
-import javax.swing.text.BadLocationException;
-import javax.swing.text.PlainDocument;
+import javax.swing.event.CaretListener;
+import javax.swing.event.DocumentListener;
+import javax.swing.text.JTextComponent;
-public class JvCacheableInputBox<E> extends JComboBox<String>
+/**
+ * A class that provides an editable combobox with a memory of previous entries
+ * that may be persisted
+ *
+ * @author tcofoegbu
+ *
+ * @param <E>
+ */
+/*
+ * (temporary?) patches to wrap a JTextField instead when running as Javascript
+ */
+public class JvCacheableInputBox<E>
{
+ protected JComboBox<String> comboBox; // used for Jalview
- private static final long serialVersionUID = 5774610435079326695L;
+ protected JTextField textField; // used for JalviewJS
- private String cacheKey;
+ protected JTextComponent textComponent; // used for both
- private AppCache appCache;
+ protected String cacheKey;
- JPanel pnlDefaultCache = new JPanel();
+ protected AppCache appCache;
- JLabel lblDefaultCacheSize = new JLabel();
+ private JPopupMenu popup = new JPopupMenu();
- JTextField txtDefaultCacheSize = new JTextField();
+ private JMenuItem menuItemClearCache = new JMenuItem();
- JPopupMenu popup = new JPopupMenu();
+ volatile boolean enterWasPressed = false;
- JMenuItem menuItemClearCache = new JMenuItem();
+ private String prototypeDisplayValue;
- final static int INPUT_LIMIT = 2;
-
- final static String SPACE = " ";
+ /**
+ * @return flag indicating if the most recent keypress was enter
+ */
+ public boolean wasEnterPressed()
+ {
+ return enterWasPressed;
+ }
- public JvCacheableInputBox(String cacheKey)
+ /**
+ * Constructor
+ *
+ * @param newCacheKey
+ */
+ public JvCacheableInputBox(String newCacheKey)
{
- super();
- this.cacheKey = cacheKey;
- setEditable(true);
- setPrototypeDisplayValue("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
- appCache = AppCache.getInstance();
+ // super();
+ cacheKey = newCacheKey;
+ prototypeDisplayValue = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
+ boolean useTextField = Platform.isJS();
+ // BH 2019.03 only switch for JavaScript here
+ // SwingJS TODO implement editable combo box
+ if (useTextField)
+ {
+ appCache = null;
+ textComponent = textField = new JTextField();
+ // {
+ // @Override
+ // public Dimension getPreferredSize() {
+ // return super.getPreferredSize();
+ //// FontMetrics fm = getFontMetrics(getFont());
+ //// return new Dimension(fm.stringWidth(prototypeDisplayValue),
+ // fm.getHeight());
+ // }
+ // };
+ }
+ else
+ {
+ appCache = AppCache.getInstance();
+ comboBox = new JComboBox<>();
+ textComponent = (JTextComponent) comboBox.getEditor()
+ .getEditorComponent();
+ comboBox.setEditable(true);
+ comboBox.addKeyListener(new KeyAdapter()
+ {
+ @Override
+ public void keyTyped(KeyEvent e)
+ {
+ enterWasPressed = false;
+ if (e.getKeyCode() == KeyEvent.VK_ENTER)
+ {
+ enterWasPressed = true;
+ }
+ // let event bubble up
+ }
+ });
+ comboBox.setPrototypeDisplayValue(prototypeDisplayValue);
+ }
initCachePopupMenu();
- appCache.initCache(cacheKey);
+ initCache(newCacheKey);
updateCache();
}
+ /**
+ * Method for initialising cache items for a given cache key and populating
+ * the in-memory cache with persisted cache items
+ *
+ * @param cacheKey
+ */
+ private void initCache(String cacheKey)
+ {
+ if (appCache == null)
+ {
+ return;
+ }
+ // obtain persisted cache items from properties file as a delimited string
+ String delimitedCacheStr = Cache.getProperty(cacheKey);
+ if (delimitedCacheStr == null || delimitedCacheStr.isEmpty())
+ {
+ return;
+ }
+ // convert delimited cache items to a list of strings
+ List<String> persistedCacheItems = Arrays
+ .asList(delimitedCacheStr.split(AppCache.CACHE_DELIMITER));
+
+ LinkedHashSet<String> foundCacheItems = appCache
+ .getAllCachedItemsFor(cacheKey);
+ if (foundCacheItems == null)
+ {
+ foundCacheItems = new LinkedHashSet<>();
+ }
+ // populate memory cache
+ for (String cacheItem : persistedCacheItems)
+ {
+ foundCacheItems.add(cacheItem);
+ }
+ appCache.putCache(cacheKey, foundCacheItems);
+ }
/**
* Initialise this cache's pop-up menu
*/
private void initCachePopupMenu()
{
- add(popup);
- setComponentPopupMenu(popup);
- pnlDefaultCache.setBackground(Color.WHITE);
- txtDefaultCacheSize.setPreferredSize(new Dimension(45, 20));
- lblDefaultCacheSize.setText(SPACE
- + MessageManager.getString("label.default_cache_size"));
- // Force input to accept only Integer entries up to length - INPUT_LIMIT
- txtDefaultCacheSize.setDocument(new PlainDocument()
+ if (appCache == null)
{
- private static final long serialVersionUID = 1L;
-
- @Override
- public void insertString(int offs, String str, AttributeSet a)
- throws BadLocationException
- {
- if (getLength() + str.length() <= INPUT_LIMIT && isInteger(str))
- {
- super.insertString(offs, str, a);
- }
- }
- });
- txtDefaultCacheSize.setText(appCache.getCacheLmit(cacheKey));
- pnlDefaultCache.add(lblDefaultCacheSize);
- pnlDefaultCache.add(txtDefaultCacheSize);
- popup.insert(pnlDefaultCache, 0);
-
- menuItemClearCache.setText(MessageManager
- .getString("action.clear_cached_items"));
+ return;
+ }
+ menuItemClearCache.setFont(new java.awt.Font("Verdana", 0, 12));
+ menuItemClearCache
+ .setText(MessageManager.getString("action.clear_cached_items"));
menuItemClearCache.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
- System.out.println(">>>>> Clear cache items");
+ // System.out.println(">>>>> Clear cache items");
setSelectedItem("");
appCache.deleteCacheItems(cacheKey);
updateCache();
});
popup.add(menuItemClearCache);
+ comboBox.setComponentPopupMenu(popup);
+ comboBox.add(popup);
}
/**
* @param text
* @return
*/
- private boolean isInteger(String text)
+ static boolean isInteger(String text)
{
try
{
*/
public void updateCache()
{
+ if (appCache == null)
+ {
+ return;
+ }
SwingUtilities.invokeLater(new Runnable()
{
@Override
public void run()
{
- int cacheLimit = appCache.updateCacheLimit(cacheKey,
- txtDefaultCacheSize.getText());
- String userInput = getUserInput();
- if (userInput != null && !userInput.isEmpty())
- {
- LinkedHashSet<String> foundCache = appCache
- .getAllCachedItemsFor(cacheKey);
- // remove old cache item so as to place current input at the top of
- // the result
- foundCache.remove(userInput);
- foundCache.add(userInput);
- appCache.putCache(cacheKey, foundCache);
- }
+ updateCacheNow();
+ }
+ });
+ }
- String lastSearch = userInput;
- if (getItemCount() > 0)
- {
- removeAllItems();
- }
- Set<String> cacheItems = appCache.getAllCachedItemsFor(cacheKey);
- List<String> reversedCacheItems = new ArrayList<String>();
- reversedCacheItems.addAll(cacheItems);
- cacheItems = null;
- Collections.reverse(reversedCacheItems);
- if (lastSearch.isEmpty())
- {
- addItem("");
- }
+ /**
+ * Updates the cached values to include the last user input as the most
+ * recently added (without duplication), and rebuilds the dropdown list with
+ * this as the first entry. If the maximum configured cache size would be
+ * exceeded, the oldest entry is removed.
+ */
+ void updateCacheNow()
+ {
+ int cacheLimit = Integer.valueOf(appCache.getCacheLimit(cacheKey));
+ String userInput = getUserInput();
+ if (userInput != null && !userInput.isEmpty())
+ {
+ LinkedHashSet<String> foundCache = appCache
+ .getAllCachedItemsFor(cacheKey);
+ // remove old cache item so as to place current input at the top of
+ // the result
+ foundCache.remove(userInput);
+ foundCache.add(userInput);
+ appCache.putCache(cacheKey, foundCache);
+ }
+
+ String lastSearch = userInput;
+ if (comboBox.getItemCount() > 0)
+ {
+ comboBox.removeAllItems();
+ }
+ Set<String> cacheItems = appCache.getAllCachedItemsFor(cacheKey);
+ List<String> reversedCacheItems = new ArrayList<>();
+ reversedCacheItems.addAll(cacheItems);
+ cacheItems = null;
+ Collections.reverse(reversedCacheItems);
+ if (lastSearch.isEmpty())
+ {
+ comboBox.addItem("");
+ }
- if (reversedCacheItems != null && !reversedCacheItems.isEmpty())
+ if (reversedCacheItems != null && !reversedCacheItems.isEmpty())
+ {
+ LinkedHashSet<String> foundCache = appCache
+ .getAllCachedItemsFor(cacheKey);
+ boolean prune = reversedCacheItems.size() > cacheLimit;
+ int count = 1;
+ boolean limitExceeded = false;
+ for (String cacheItem : reversedCacheItems)
+ {
+ limitExceeded = (count++ > cacheLimit);
+ if (prune)
{
- LinkedHashSet<String> foundCache = appCache
- .getAllCachedItemsFor(cacheKey);
- boolean prune = reversedCacheItems.size() > cacheLimit;
- int count = 1;
- boolean limitExceeded = false;
- for (String cacheItem : reversedCacheItems)
+ if (limitExceeded)
+ {
+ foundCache.remove(cacheItem);
+ }
+ else
{
- limitExceeded = (count++ > cacheLimit);
- if (prune)
- {
- if (limitExceeded)
- {
- foundCache.remove(cacheItem);
- }
- else
- {
- addItem(cacheItem);
- }
- }
- else
- {
- addItem(cacheItem);
- }
+ comboBox.addItem(cacheItem);
}
- appCache.putCache(cacheKey, foundCache);
}
- setSelectedItem(lastSearch.isEmpty() ? "" : lastSearch);
+ else
+ {
+ comboBox.addItem(cacheItem);
+ }
}
- });
- }
+ appCache.putCache(cacheKey, foundCache);
+ }
+ setSelectedItem(lastSearch.isEmpty() ? "" : lastSearch);
+ }
/**
* This method should be called to persist the in-memory cache when this
*/
public void persistCache()
{
+ if (appCache == null)
+ {
+ return;
+ }
appCache.persistCache(cacheKey);
- appCache.updateCacheLimit(cacheKey, txtDefaultCacheSize.getText());
}
/**
- * Method to obtain input text from the cache box
+ * Returns the trimmed text in the input field
*
* @return
*/
public String getUserInput()
{
- return getEditor().getItem() == null ? "" : getEditor().getItem()
- .toString().trim();
+ if (comboBox == null)
+ {
+ return textField.getText().trim();
+ }
+ Object item = comboBox.getEditor().getItem();
+ return item == null ? "" : item.toString().trim();
+ }
+
+ public JComponent getComponent()
+ {
+ return (comboBox == null ? textField : comboBox);
+ }
+
+ public void addActionListener(ActionListener actionListener)
+ {
+ if (comboBox == null)
+ {
+ textField.addActionListener(actionListener);
+ }
+ else
+ {
+ comboBox.addActionListener(actionListener);
+ }
+ }
+
+ public void addDocumentListener(DocumentListener listener)
+ {
+ textComponent.getDocument().addDocumentListener(listener);
+ }
+
+ public void addFocusListener(FocusListener focusListener)
+ {
+ textComponent.addFocusListener(focusListener);
+ }
+
+ public void addKeyListener(KeyListener kl)
+ {
+ textComponent.addKeyListener(kl);
+ }
+
+ public void addCaretListener(CaretListener caretListener)
+ {
+ textComponent.addCaretListener(caretListener);
+ }
+
+ public void setEditable(boolean b)
+ {
+ if (comboBox != null)
+ {
+ comboBox.setEditable(b);
+ }
+ }
+
+ public void setPrototypeDisplayValue(String string)
+ {
+ prototypeDisplayValue = string;
+ if (comboBox != null)
+ {
+ comboBox.setPrototypeDisplayValue(string);
+ }
+ }
+
+ public void setSelectedItem(String userInput)
+ {
+ if (comboBox != null)
+ {
+ comboBox.setSelectedItem(userInput);
+ }
+ }
+
+ public boolean isPopupVisible()
+ {
+ return (comboBox != null && comboBox.isPopupVisible());
+ }
+
+ public void addItem(String item)
+ {
+ if (comboBox != null)
+ {
+ comboBox.addItem(item);
+ }
}
}