2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
21 package jalview.io.cache;
23 import java.awt.Dimension;
24 import java.awt.FontMetrics;
25 import java.awt.event.ActionEvent;
26 import java.awt.event.ActionListener;
27 import java.awt.event.FocusListener;
28 import java.awt.event.KeyAdapter;
29 import java.awt.event.KeyEvent;
30 import java.awt.event.KeyListener;
31 import java.util.ArrayList;
32 import java.util.Arrays;
33 import java.util.Collections;
34 import java.util.LinkedHashSet;
35 import java.util.List;
38 import javax.swing.JComboBox;
39 import javax.swing.JComponent;
40 import javax.swing.JMenuItem;
41 import javax.swing.JPopupMenu;
42 import javax.swing.JTextField;
43 import javax.swing.SwingUtilities;
44 import javax.swing.event.CaretListener;
45 import javax.swing.event.DocumentListener;
46 import javax.swing.text.JTextComponent;
48 import jalview.bin.Cache;
49 import jalview.util.MessageManager;
50 import jalview.util.Platform;
53 * A class that provides an editable combobox with a memory of previous entries
54 * that may be persisted
61 * (temporary?) patches to wrap a JTextField instead when running as Javascript
63 public class JvCacheableInputBox<E>
65 protected JComboBox<String> comboBox; // used for Jalview
67 protected JTextField textField; // used for JalviewJS
69 protected JTextComponent textComponent; // used for both
71 protected String cacheKey;
73 protected AppCache appCache;
75 private JPopupMenu popup = new JPopupMenu();
77 private JMenuItem menuItemClearCache = new JMenuItem();
79 volatile boolean enterWasPressed = false;
81 private String prototypeDisplayValue;
84 * @return flag indicating if the most recent keypress was enter
86 public boolean wasEnterPressed()
88 return enterWasPressed;
92 * Constructor given the key to cached values, and the (approximate) length in
93 * characters of the input field
98 public JvCacheableInputBox(String newCacheKey, int length)
101 cacheKey = newCacheKey;
102 prototypeDisplayValue = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
105 StringBuilder sb = new StringBuilder();
106 for (int i = 0; i < length; i++)
110 setPrototypeDisplayValue(sb.toString());
112 boolean useTextField = Platform.isJS();
113 // BH 2019.03 only switch for JavaScript here
114 // SwingJS TODO implement editable combo box
118 textComponent = textField = new JTextField();
119 FontMetrics fm = textField.getFontMetrics(textField.getFont());
120 textField.setPreferredSize(new Dimension(
121 fm.stringWidth(prototypeDisplayValue), fm.getHeight() + 4));
124 // public Dimension getPreferredSize() {
125 // return super.getPreferredSize();
126 //// FontMetrics fm = getFontMetrics(getFont());
127 //// return new Dimension(fm.stringWidth(prototypeDisplayValue),
134 appCache = AppCache.getInstance();
135 comboBox = new JComboBox<>();
136 textComponent = (JTextComponent) comboBox.getEditor()
137 .getEditorComponent();
138 comboBox.setEditable(true);
139 comboBox.addKeyListener(new KeyAdapter()
142 public void keyTyped(KeyEvent e)
144 enterWasPressed = false;
145 if (e.getKeyCode() == KeyEvent.VK_ENTER)
147 enterWasPressed = true;
149 // let event bubble up
152 comboBox.setPrototypeDisplayValue(prototypeDisplayValue);
153 initCachePopupMenu();
154 initCache(newCacheKey);
160 * Method for initialising cache items for a given cache key and populating
161 * the in-memory cache with persisted cache items
165 private void initCache(String cacheKey)
167 if (appCache == null)
171 // obtain persisted cache items from properties file as a delimited string
172 String delimitedCacheStr = Cache.getProperty(cacheKey);
173 if (delimitedCacheStr == null || delimitedCacheStr.isEmpty())
177 // convert delimited cache items to a list of strings
178 List<String> persistedCacheItems = Arrays
179 .asList(delimitedCacheStr.split(AppCache.CACHE_DELIMITER));
181 LinkedHashSet<String> foundCacheItems = appCache
182 .getAllCachedItemsFor(cacheKey);
183 if (foundCacheItems == null)
185 foundCacheItems = new LinkedHashSet<>();
187 // populate memory cache
188 for (String cacheItem : persistedCacheItems)
190 foundCacheItems.add(cacheItem);
192 appCache.putCache(cacheKey, foundCacheItems);
196 * Initialise this cache's pop-up menu
198 private void initCachePopupMenu()
200 if (appCache == null)
204 menuItemClearCache.setFont(new java.awt.Font("Verdana", 0, 12));
206 .setText(MessageManager.getString("action.clear_cached_items"));
207 menuItemClearCache.addActionListener(new ActionListener()
210 public void actionPerformed(ActionEvent e)
212 // jalview.bin.Console.outPrintln(">>>>> Clear cache items");
214 appCache.deleteCacheItems(cacheKey);
219 popup.add(menuItemClearCache);
220 comboBox.setComponentPopupMenu(popup);
225 * Answers true if input text is an integer
230 static boolean isInteger(String text)
234 Integer.parseInt(text);
236 } catch (NumberFormatException e)
243 * Method called to update the cache with the last user input
245 public void updateCache()
247 if (appCache == null)
251 SwingUtilities.invokeLater(new Runnable()
256 int cacheLimit = Integer.parseInt(appCache.getCacheLimit(cacheKey));
257 String userInput = getUserInput();
258 if (userInput != null && !userInput.isEmpty())
260 LinkedHashSet<String> foundCache = appCache
261 .getAllCachedItemsFor(cacheKey);
262 // remove old cache item so as to place current input at the top of
264 foundCache.remove(userInput);
265 foundCache.add(userInput);
266 appCache.putCache(cacheKey, foundCache);
269 String lastSearch = userInput;
270 if (comboBox.getItemCount() > 0)
272 comboBox.removeAllItems();
274 Set<String> cacheItems = appCache.getAllCachedItemsFor(cacheKey);
275 List<String> reversedCacheItems = new ArrayList<>();
276 reversedCacheItems.addAll(cacheItems);
278 Collections.reverse(reversedCacheItems);
279 if (lastSearch.isEmpty())
281 comboBox.addItem("");
284 if (reversedCacheItems != null && !reversedCacheItems.isEmpty())
286 LinkedHashSet<String> foundCache = appCache
287 .getAllCachedItemsFor(cacheKey);
288 boolean prune = reversedCacheItems.size() > cacheLimit;
290 boolean limitExceeded = false;
291 for (String cacheItem : reversedCacheItems)
293 limitExceeded = (count++ > cacheLimit);
298 foundCache.remove(cacheItem);
302 comboBox.addItem(cacheItem);
307 comboBox.addItem(cacheItem);
310 appCache.putCache(cacheKey, foundCache);
312 setSelectedItem(lastSearch.isEmpty() ? "" : lastSearch);
318 * This method should be called to persist the in-memory cache when this
319 * components parent frame is closed / exited
321 public void persistCache()
323 if (appCache == null)
327 appCache.persistCache(cacheKey);
331 * Returns the trimmed text in the input field
335 public String getUserInput()
337 if (comboBox == null)
339 return textField.getText().trim();
341 Object item = comboBox.getEditor().getItem();
342 return item == null ? "" : item.toString().trim();
345 public JComponent getComponent()
347 return (comboBox == null ? textField : comboBox);
350 public void addActionListener(ActionListener actionListener)
352 if (comboBox == null)
354 textField.addActionListener(actionListener);
358 comboBox.addActionListener(actionListener);
362 public void addDocumentListener(DocumentListener listener)
364 textComponent.getDocument().addDocumentListener(listener);
367 public void addFocusListener(FocusListener focusListener)
369 textComponent.addFocusListener(focusListener);
372 public void addKeyListener(KeyListener kl)
374 textComponent.addKeyListener(kl);
377 public void addCaretListener(CaretListener caretListener)
379 textComponent.addCaretListener(caretListener);
382 public void setEditable(boolean b)
384 if (comboBox != null)
386 comboBox.setEditable(b);
390 public void setPrototypeDisplayValue(String string)
392 prototypeDisplayValue = string;
393 if (comboBox != null)
395 comboBox.setPrototypeDisplayValue(string);
399 public void setSelectedItem(String userInput)
401 if (comboBox != null)
403 comboBox.setSelectedItem(userInput);
407 public boolean isPopupVisible()
409 return (comboBox != null && comboBox.isPopupVisible());
412 public void addItem(String item)
414 if (comboBox != null)
416 comboBox.addItem(item);