diff --git a/gui/griffon-app/controllers/com/muwire/gui/BrowseController.groovy b/gui/griffon-app/controllers/com/muwire/gui/BrowseController.groovy index 0364d605..b575e77f 100644 --- a/gui/griffon-app/controllers/com/muwire/gui/BrowseController.groovy +++ b/gui/griffon-app/controllers/com/muwire/gui/BrowseController.groovy @@ -1,5 +1,6 @@ package com.muwire.gui +import com.muwire.core.SplitPattern import griffon.core.artifact.GriffonController import griffon.core.controller.ControllerAction import griffon.inject.MVCMember @@ -9,7 +10,6 @@ import net.i2p.data.Base64 import javax.annotation.Nonnull import com.muwire.core.Core -import com.muwire.core.EventBus import com.muwire.core.download.UIDownloadEvent import com.muwire.core.search.BrowseStatus import com.muwire.core.search.BrowseStatusEvent @@ -17,8 +17,12 @@ import com.muwire.core.search.UIBrowseEvent import com.muwire.core.search.UIResultBatchEvent import com.muwire.core.search.UIResultEvent +import javax.swing.JTextField + @ArtifactProviderFor(GriffonController) class BrowseController { + @MVCMember @Nonnull + FactoryBuilderSupport builder @MVCMember @Nonnull BrowseModel model @MVCMember @Nonnull @@ -44,6 +48,8 @@ class BrowseController { return runInsideUIAsync { model.status = e.status + model.filterEnabled = (e.status == BrowseStatus.FINISHED || e.status == BrowseStatus.FAILED) && + model.resultCount > 0 if (e.status == BrowseStatus.FETCHING) model.totalResults = e.totalResults } @@ -54,25 +60,37 @@ class BrowseController { if (e.uuid != model.uuid) return model.chatActionEnabled = e.results[0].chat - model.results.addAll(e.results.toList()) + List results = e.results.toList() + model.results.addAll(results) + synchronized (model.allResults) { + model.allResults.addAll(results) + } model.resultCount = model.results.size() - int [] selectedRows = view.resultsTable.getSelectedRows() - if (view.lastSortEvent != null) { - for (int i = 0; i < selectedRows.length; i ++) - selectedRows[i] = view.resultsTable.rowSorter.convertRowIndexToModel(selectedRows[i]) - } - view.resultsTable.model.fireTableDataChanged() - if (view.lastSortEvent != null) { - for (int i = 0; i < selectedRows.length; i ++) - selectedRows[i] = view.resultsTable.rowSorter.convertRowIndexToView(selectedRows[i]) - } - for (int row : selectedRows) - view.resultsTable.selectionModel.addSelectionInterval(row, row) - + view.refreshResults() } } + @ControllerAction + void filter() { + JTextField field = builder.getVariable("filter-field") + String filter = field.getText() + if (filter == null) + return + filter = filter.strip().replaceAll(SplitPattern.SPLIT_PATTERN," ").toLowerCase() + String [] split = filter.split(" ") + def hs = new HashSet() + split.each {if (it.length() > 0) hs << it} + model.filter = hs.toArray(new String[0]) + model.filterResults() + } + + @ControllerAction + void clearFilter() { + model.filter = null + model.filterResults() + } + @ControllerAction void download() { def selectedResults = view.selectedResults() diff --git a/gui/griffon-app/models/com/muwire/gui/BrowseModel.groovy b/gui/griffon-app/models/com/muwire/gui/BrowseModel.groovy index 097fe7da..2877e6e9 100644 --- a/gui/griffon-app/models/com/muwire/gui/BrowseModel.groovy +++ b/gui/griffon-app/models/com/muwire/gui/BrowseModel.groovy @@ -1,15 +1,22 @@ package com.muwire.gui import com.muwire.core.Persona - +import com.muwire.core.search.UIResultEvent import griffon.core.artifact.GriffonModel +import griffon.inject.MVCMember import griffon.transform.Observable import griffon.metadata.ArtifactProviderFor import com.muwire.core.search.BrowseStatus +import javax.annotation.Nonnull +import javax.swing.SwingWorker + @ArtifactProviderFor(GriffonModel) class BrowseModel { + @MVCMember @Nonnull + BrowseView view + Persona host @Observable BrowseStatus status @Observable boolean downloadActionEnabled @@ -19,7 +26,71 @@ class BrowseModel { @Observable boolean chatActionEnabled @Observable int totalResults @Observable int resultCount + @Observable boolean filterEnabled volatile UUID uuid def results = [] + List allResults = [] + + volatile String[] filter + volatile Filterer filterer + + private boolean filter(UIResultEvent result) { + if (filter == null) + return true + String name = result.getName().toLowerCase() + boolean contains = true + for (String keyword : filter) { + contains &= name.contains(keyword) + } + contains + } + + void filterResults() { + results.clear() + filterer?.cancel() + if (filter != null) { + filterer = new Filterer() + filterer.execute() + } else { + synchronized (allResults) { + results.addAll(allResults) + } + view.refreshResults() + } + } + + private class Filterer extends SwingWorker, UIResultEvent> { + private volatile boolean cancelled; + + void cancel() { + cancelled = true + } + + @Override + protected List doInBackground() throws Exception { + synchronized (allResults) { + for (UIResultEvent result : allResults) { + if (cancelled) + break + if (filter(result)) + publish(result) + } + } + } + + @Override + protected void process(List chunks) { + if (cancelled) + return + results.addAll(chunks) + } + + @Override + protected void done() { + if (cancelled) + return + view.refreshResults() + } + } } \ No newline at end of file diff --git a/gui/griffon-app/views/com/muwire/gui/BrowseView.groovy b/gui/griffon-app/views/com/muwire/gui/BrowseView.groovy index 4b12fa8b..8feff7d1 100644 --- a/gui/griffon-app/views/com/muwire/gui/BrowseView.groovy +++ b/gui/griffon-app/views/com/muwire/gui/BrowseView.groovy @@ -1,6 +1,10 @@ package com.muwire.gui +import com.muwire.core.search.BrowseStatus import griffon.core.artifact.GriffonView + +import javax.swing.JTextField + import static com.muwire.gui.Translator.trans import griffon.inject.MVCMember import griffon.metadata.ArtifactProviderFor @@ -63,13 +67,25 @@ class BrowseView { } } panel (constraints : BorderLayout.SOUTH) { - button(text : trans("DOWNLOAD"), enabled : bind {model.downloadActionEnabled}, downloadAction) - button(text : trans("VIEW_COMMENT"), enabled : bind{model.viewCommentActionEnabled}, viewCommentAction) - button(text : trans("VIEW_CERTIFICATES"), enabled : bind{model.viewCertificatesActionEnabled}, viewCertificatesAction) - button(text : trans("VIEW_COLLECTIONS"), enabled : bind{model.viewCollectionsActionEnabled}, viewCollectionsAction) - button(text : trans("CHAT"), enabled : bind {model.chatActionEnabled}, chatAction) - label(text : trans("DOWNLOAD_SEQUENTIALLY")) - sequentialDownloadCheckbox = checkBox() + gridLayout(rows: 1, cols: 3) + panel(border: etchedBorder()) { + button(text: trans("DOWNLOAD"), enabled: bind { model.downloadActionEnabled }, downloadAction) + label(text: trans("DOWNLOAD_SEQUENTIALLY"), enabled: bind {model.downloadActionEnabled}) + sequentialDownloadCheckbox = checkBox(enabled: bind {model.downloadActionEnabled}) + } + panel(border: etchedBorder()) { + button(text: trans("VIEW_COMMENT"), enabled: bind { model.viewCommentActionEnabled }, viewCommentAction) + button(text: trans("VIEW_CERTIFICATES"), enabled: bind { model.viewCertificatesActionEnabled }, viewCertificatesAction) + button(text: trans("VIEW_COLLECTIONS"), enabled: bind { model.viewCollectionsActionEnabled }, viewCollectionsAction) + button(text: trans("CHAT"), enabled: bind { model.chatActionEnabled }, chatAction) + } + panel(border: etchedBorder()) { + def textField = new JTextField(15) + textField.addActionListener({ controller.filter() }) + widget(id: "filter-field", enabled: bind { model.filterEnabled }, textField) + button(text: trans("FILTER"), enabled: bind { model.filterEnabled }, filterAction) + button(text: trans("CLEAR"), enabled: bind { model.filterEnabled }, clearFilterAction) + } } } @@ -217,6 +233,20 @@ class BrowseView { parent.setTabComponentAt(index, tabPanel) } + void refreshResults() { + int [] selectedRows = resultsTable.getSelectedRows() + if (lastSortEvent != null) { + for (int i = 0; i < selectedRows.length; i ++) + selectedRows[i] = resultsTable.rowSorter.convertRowIndexToModel(selectedRows[i]) + } + resultsTable.model.fireTableDataChanged() + if (lastSortEvent != null) { + for (int i = 0; i < selectedRows.length; i ++) + selectedRows[i] = resultsTable.rowSorter.convertRowIndexToView(selectedRows[i]) + } + for (int row : selectedRows) + resultsTable.selectionModel.addSelectionInterval(row, row) + } def selectedResults() { int [] rows = resultsTable.getSelectedRows()