From e22d5fea119a39cefb6c2f08b81e73f0989f1ded Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Sun, 3 Nov 2019 01:50:55 +0000 Subject: [PATCH] better search box --- .../com/muwire/core/MuWireSettings.groovy | 34 ++----- .../java/com/muwire/core/util/DataUtil.java | 22 +++++ .../com/muwire/gui/MainFrameController.groovy | 10 +- .../views/com/muwire/gui/MainFrameView.groovy | 13 ++- .../groovy/com/muwire/gui/SearchField.groovy | 14 +++ .../com/muwire/gui/SearchFieldEditor.groovy | 47 +++++++++ .../com/muwire/gui/SearchFieldModel.groovy | 96 +++++++++++++++++++ .../groovy/com/muwire/gui/UISettings.groovy | 6 ++ 8 files changed, 213 insertions(+), 29 deletions(-) create mode 100644 gui/src/main/groovy/com/muwire/gui/SearchField.groovy create mode 100644 gui/src/main/groovy/com/muwire/gui/SearchFieldEditor.groovy create mode 100644 gui/src/main/groovy/com/muwire/gui/SearchFieldModel.groovy diff --git a/core/src/main/groovy/com/muwire/core/MuWireSettings.groovy b/core/src/main/groovy/com/muwire/core/MuWireSettings.groovy index b154f17b..31a7569c 100644 --- a/core/src/main/groovy/com/muwire/core/MuWireSettings.groovy +++ b/core/src/main/groovy/com/muwire/core/MuWireSettings.groovy @@ -78,10 +78,10 @@ class MuWireSettings { totalUploadSlots = Integer.valueOf(props.getProperty("totalUploadSlots","-1")) uploadSlotsPerUser = Integer.valueOf(props.getProperty("uploadSlotsPerUser","-1")) - watchedDirectories = readEncodedSet(props, "watchedDirectories") - watchedKeywords = readEncodedSet(props, "watchedKeywords") - watchedRegexes = readEncodedSet(props, "watchedRegexes") - negativeFileTree = readEncodedSet(props, "negativeFileTree") + watchedDirectories = DataUtil.readEncodedSet(props, "watchedDirectories") + watchedKeywords = DataUtil.readEncodedSet(props, "watchedKeywords") + watchedRegexes = DataUtil.readEncodedSet(props, "watchedRegexes") + negativeFileTree = DataUtil.readEncodedSet(props, "negativeFileTree") trustSubscriptions = new HashSet<>() if (props.containsKey("trustSubscriptions")) { @@ -125,10 +125,10 @@ class MuWireSettings { props.setProperty("totalUploadSlots", String.valueOf(totalUploadSlots)) props.setProperty("uploadSlotsPerUser", String.valueOf(uploadSlotsPerUser)) - writeEncodedSet(watchedDirectories, "watchedDirectories", props) - writeEncodedSet(watchedKeywords, "watchedKeywords", props) - writeEncodedSet(watchedRegexes, "watchedRegexes", props) - writeEncodedSet(negativeFileTree, "negativeFileTree", props) + DataUtil.writeEncodedSet(watchedDirectories, "watchedDirectories", props) + DataUtil.writeEncodedSet(watchedKeywords, "watchedKeywords", props) + DataUtil.writeEncodedSet(watchedRegexes, "watchedRegexes", props) + DataUtil.writeEncodedSet(negativeFileTree, "negativeFileTree", props) if (!trustSubscriptions.isEmpty()) { String encoded = trustSubscriptions.stream(). @@ -139,24 +139,6 @@ class MuWireSettings { props.store(out, "This file is UTF-8") } - - private static Set readEncodedSet(Properties props, String property) { - Set rv = new ConcurrentHashSet<>() - if (props.containsKey(property)) { - String[] encoded = props.getProperty(property).split(",") - encoded.each { rv << DataUtil.readi18nString(Base64.decode(it)) } - } - rv - } - - private static void writeEncodedSet(Set set, String property, Properties props) { - if (set.isEmpty()) - return - String encoded = set.stream(). - map({Base64.encode(DataUtil.encodei18nString(it))}). - collect(Collectors.joining(",")) - props.setProperty(property, encoded) - } boolean isLeaf() { isLeaf diff --git a/core/src/main/java/com/muwire/core/util/DataUtil.java b/core/src/main/java/com/muwire/core/util/DataUtil.java index 63e2673d..f9655220 100644 --- a/core/src/main/java/com/muwire/core/util/DataUtil.java +++ b/core/src/main/java/com/muwire/core/util/DataUtil.java @@ -11,10 +11,14 @@ import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; +import java.util.Properties; +import java.util.Set; +import java.util.stream.Collectors; import com.muwire.core.Constants; import net.i2p.data.Base64; +import net.i2p.util.ConcurrentHashSet; public class DataUtil { @@ -165,4 +169,22 @@ public class DataUtil { } catch(Exception ex) { } cb = null; } + + public static Set readEncodedSet(Properties props, String property) { + Set rv = new ConcurrentHashSet<>(); + if (props.containsKey(property)) { + String [] encoded = props.getProperty(property).split(","); + for(String s : encoded) + rv.add(readi18nString(Base64.decode(s))); + } + return rv; + } + + public static void writeEncodedSet(Set set, String property, Properties props) { + if (set.isEmpty()) + return; + String encoded = set.stream().map(s -> Base64.encode(encodei18nString(s))) + .collect(Collectors.joining(",")); + props.setProperty(property, encoded); + } } diff --git a/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy b/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy index 72302353..692c3920 100644 --- a/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy +++ b/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy @@ -11,6 +11,7 @@ import net.i2p.crypto.DSAEngine import net.i2p.data.Base64 import net.i2p.data.Signature +import java.awt.event.ActionEvent import java.nio.charset.StandardCharsets import javax.annotation.Nonnull @@ -51,11 +52,16 @@ class MainFrameController { private volatile Core core @ControllerAction - void search() { + void search(ActionEvent evt) { + if (evt.getActionCommand() == null) + return def cardsPanel = builder.getVariable("cards-panel") cardsPanel.getLayout().show(cardsPanel, "search window") - def search = builder.getVariable("search-field").text + def searchField = builder.getVariable("search-field") + def search = searchField.getSelectedItem() + searchField.model.addElement(search) + search = search.trim() if (search.length() == 0) return diff --git a/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy b/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy index 2f372880..997572a2 100644 --- a/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy +++ b/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy @@ -11,6 +11,7 @@ import net.i2p.data.DataHelper import javax.swing.BorderFactory import javax.swing.Box import javax.swing.BoxLayout +import javax.swing.JComboBox import javax.swing.JFileChooser import javax.swing.JFrame import javax.swing.JLabel @@ -24,6 +25,8 @@ import javax.swing.SwingConstants import javax.swing.SwingUtilities import javax.swing.TransferHandler import javax.swing.border.Border +import javax.swing.event.DocumentEvent +import javax.swing.event.DocumentListener import javax.swing.event.TreeExpansionEvent import javax.swing.event.TreeExpansionListener import javax.swing.table.DefaultTableCellRenderer @@ -135,7 +138,11 @@ class MainFrameView { panel(constraints: BorderLayout.CENTER) { borderLayout() label(" Enter search here:", constraints: BorderLayout.WEST) // TODO: fix this - textField(id: "search-field", constraints: BorderLayout.CENTER, action : searchAction) + + def searchFieldModel = new SearchFieldModel(settings, new File(application.context.get("muwire-home"))) + JComboBox myComboBox = new SearchField(searchFieldModel) + myComboBox.setAction(searchAction) + widget(id: "search-field", constraints: BorderLayout.CENTER, myComboBox) } panel( constraints: BorderLayout.EAST) { @@ -484,6 +491,10 @@ class MainFrameView { } }}) + // search field + def searchField = builder.getVariable("search-field") + + // downloads table def downloadsTable = builder.getVariable("downloads-table") def selectionModel = downloadsTable.getSelectionModel() selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION) diff --git a/gui/src/main/groovy/com/muwire/gui/SearchField.groovy b/gui/src/main/groovy/com/muwire/gui/SearchField.groovy new file mode 100644 index 00000000..24a40eb1 --- /dev/null +++ b/gui/src/main/groovy/com/muwire/gui/SearchField.groovy @@ -0,0 +1,14 @@ +package com.muwire.gui + +import java.awt.event.KeyEvent + +import javax.swing.JComboBox + +class SearchField extends JComboBox { + SearchField(SearchFieldModel model) { + super() + setEditable(true) + setModel(model) + setEditor(new SearchFieldEditor(model, this)) + } +} diff --git a/gui/src/main/groovy/com/muwire/gui/SearchFieldEditor.groovy b/gui/src/main/groovy/com/muwire/gui/SearchFieldEditor.groovy new file mode 100644 index 00000000..26cc01c1 --- /dev/null +++ b/gui/src/main/groovy/com/muwire/gui/SearchFieldEditor.groovy @@ -0,0 +1,47 @@ +package com.muwire.gui + +import javax.swing.JTextField +import javax.swing.SwingUtilities +import javax.swing.event.DocumentEvent +import javax.swing.event.DocumentListener +import javax.swing.plaf.basic.BasicComboBoxEditor + +class SearchFieldEditor extends BasicComboBoxEditor { + + private final SearchFieldModel model + private final SearchField field + + SearchFieldEditor(SearchFieldModel model, SearchField field) { + super() + this.model = model + this.field = field + def action = field.getAction() + field.setAction(null) + editor.setAction(action) + editor.getDocument().addDocumentListener(new DocumentListener() { + + @Override + public void insertUpdate(DocumentEvent e) { + SwingUtilities.invokeLater({ + field.hidePopup() + if (model.onKeyStroke(editor.text)) + field.showPopup() + }) + } + + @Override + public void removeUpdate(DocumentEvent e) { + SwingUtilities.invokeLater({ + field.hidePopup() + if (model.onKeyStroke(editor.text)) + field.showPopup() + }) + } + + @Override + public void changedUpdate(DocumentEvent e) { + } + + }) + } +} diff --git a/gui/src/main/groovy/com/muwire/gui/SearchFieldModel.groovy b/gui/src/main/groovy/com/muwire/gui/SearchFieldModel.groovy new file mode 100644 index 00000000..d5a06d93 --- /dev/null +++ b/gui/src/main/groovy/com/muwire/gui/SearchFieldModel.groovy @@ -0,0 +1,96 @@ +package com.muwire.gui + +import javax.swing.AbstractListModel +import javax.swing.MutableComboBoxModel + +class SearchFieldModel extends AbstractListModel implements MutableComboBoxModel { + private final UISettings uiSettings + private final File settingsFile + private final List objects = new ArrayList<>() + private String selectedObject + + SearchFieldModel(UISettings uiSettings, File home) { + super() + this.uiSettings = uiSettings + this.settingsFile = new File(home, "gui.properties") + uiSettings.searchHistory.each { objects.add(it) } + fireIntervalAdded(this, 0, objects.size() - 1) + } + + public void addElement(Object string) { + if (!uiSettings.searchHistory.add(string)) + return + settingsFile.withOutputStream { uiSettings.write(it) } + objects.add(string); + fireIntervalAdded(this,objects.size()-1, objects.size()-1); + if ( objects.size() == 1 && selectedObject == null && string != null ) { + setSelectedItem( string ); + } + } + + boolean onKeyStroke(String selected) { + selectedObject = selected + if (selected == null || selected.length() == 0) { + objects.clear() + uiSettings.searchHistory.each { objects.add(it) } + return true + } + + objects.clear() + + Set matching = new HashSet<>(uiSettings.searchHistory) + matching.retainAll { it.contains(selected) } + + matching.each { + objects.add(it) + } + Collections.sort(objects) + if (!objects.isEmpty()) { + fireIntervalAdded(this, 0, objects.size() - 1) + return true + } + false + } + + @Override + public void setSelectedItem(Object anObject) { + if ((selectedObject != null && !selectedObject.equals( anObject )) || + selectedObject == null && anObject != null) { + selectedObject = anObject; + fireContentsChanged(this, -1, -1); + } + } + + @Override + public Object getSelectedItem() { + selectedObject + } + + @Override + public int getSize() { + objects.size() + } + + @Override + public Object getElementAt(int index) { + if ( index >= 0 && index < objects.size() ) + return objects.get(index); + else + return null; + } + + @Override + public void removeElement(Object obj) { + + } + + @Override + public void insertElementAt(Object item, int index) { + + } + + @Override + public void removeElementAt(int index) { + + } +} diff --git a/gui/src/main/groovy/com/muwire/gui/UISettings.groovy b/gui/src/main/groovy/com/muwire/gui/UISettings.groovy index 5c096330..f56a82b7 100644 --- a/gui/src/main/groovy/com/muwire/gui/UISettings.groovy +++ b/gui/src/main/groovy/com/muwire/gui/UISettings.groovy @@ -1,5 +1,7 @@ package com.muwire.gui +import com.muwire.core.util.DataUtil + class UISettings { String lnf @@ -14,6 +16,7 @@ class UISettings { boolean closeWarning boolean exitOnClose boolean clearUploads + Set searchHistory UISettings(Properties props) { lnf = props.getProperty("lnf", "system") @@ -28,6 +31,8 @@ class UISettings { closeWarning = Boolean.parseBoolean(props.getProperty("closeWarning","true")) exitOnClose = Boolean.parseBoolean(props.getProperty("exitOnClose","false")) clearUploads = Boolean.parseBoolean(props.getProperty("clearUploads","false")) + + searchHistory = DataUtil.readEncodedSet(props, "searchHistory") } void write(OutputStream out) throws IOException { @@ -46,6 +51,7 @@ class UISettings { if (font != null) props.setProperty("font", font) + DataUtil.writeEncodedSet(searchHistory, "searchHistory", props) props.store(out, "UI Properties") }