diff --git a/core/src/main/groovy/com/muwire/core/search/ResultsSender.groovy b/core/src/main/groovy/com/muwire/core/search/ResultsSender.groovy index 9e7ab3d2..a775fe29 100644 --- a/core/src/main/groovy/com/muwire/core/search/ResultsSender.groovy +++ b/core/src/main/groovy/com/muwire/core/search/ResultsSender.groovy @@ -55,7 +55,8 @@ class ResultsSender { name : it.getFile().getName(), size : length, infohash : it.getInfoHash(), - pieceSize : FileHasher.getPieceSize(length) + pieceSize : FileHasher.getPieceSize(length), + uuid : uuid ) eventBus.publish(uiResultEvent) } diff --git a/gui/griffon-app/conf/Config.groovy b/gui/griffon-app/conf/Config.groovy index 4e7d2d42..9e4af1b1 100644 --- a/gui/griffon-app/conf/Config.groovy +++ b/gui/griffon-app/conf/Config.groovy @@ -16,4 +16,9 @@ mvcGroups { view = 'com.muwire.gui.MainFrameView' controller = 'com.muwire.gui.MainFrameController' } + 'SearchTab' { + model = 'com.muwire.gui.SearchTabModel' + view = 'com.muwire.gui.SearchTabView' + controller = 'com.muwire.gui.SearchTabController' + } } \ No newline at end of file diff --git a/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy b/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy index b50e3365..46da4371 100644 --- a/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy +++ b/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy @@ -3,6 +3,8 @@ package com.muwire.gui import griffon.core.GriffonApplication import griffon.core.artifact.GriffonController import griffon.core.controller.ControllerAction +import griffon.core.mvc.MVCGroup +import griffon.core.mvc.MVCGroupConfiguration import griffon.inject.MVCMember import griffon.metadata.ArtifactProviderFor import javax.annotation.Nonnull @@ -30,7 +32,14 @@ class MainFrameController { @ControllerAction void search() { def search = builder.getVariable("search-field").text - def searchEvent = new SearchEvent(searchTerms : [search], uuid : UUID.randomUUID()) + def uuid = UUID.randomUUID() + Map params = new HashMap<>() + params["search-terms"] = search + params["uuid"] = uuid.toString() + def group = mvcGroup.createMVCGroup("SearchTab", uuid.toString(), params) + model.results[uuid.toString()] = group + + def searchEvent = new SearchEvent(searchTerms : [search], uuid : uuid) core.eventBus.publish(new QueryEvent(searchEvent : searchEvent, firstHop : true, replyTo: core.me.destination, receivedOn: core.me.destination)) } diff --git a/gui/griffon-app/controllers/com/muwire/gui/SearchTabController.groovy b/gui/griffon-app/controllers/com/muwire/gui/SearchTabController.groovy new file mode 100644 index 00000000..e6271942 --- /dev/null +++ b/gui/griffon-app/controllers/com/muwire/gui/SearchTabController.groovy @@ -0,0 +1,11 @@ +package com.muwire.gui + +import griffon.core.artifact.GriffonController +import griffon.core.controller.ControllerAction +import griffon.inject.MVCMember +import griffon.metadata.ArtifactProviderFor +import javax.annotation.Nonnull + +@ArtifactProviderFor(GriffonController) +class SearchTabController { +} \ No newline at end of file diff --git a/gui/griffon-app/models/com/muwire/gui/MainFrameModel.groovy b/gui/griffon-app/models/com/muwire/gui/MainFrameModel.groovy index d10eb9ce..6eef7bea 100644 --- a/gui/griffon-app/models/com/muwire/gui/MainFrameModel.groovy +++ b/gui/griffon-app/models/com/muwire/gui/MainFrameModel.groovy @@ -1,5 +1,7 @@ package com.muwire.gui +import java.util.concurrent.ConcurrentHashMap + import javax.annotation.Nonnull import javax.inject.Inject import javax.swing.JTable @@ -21,6 +23,7 @@ import com.muwire.core.upload.UploadFinishedEvent import griffon.core.GriffonApplication import griffon.core.artifact.GriffonModel +import griffon.core.mvc.MVCGroup import griffon.inject.MVCMember import griffon.transform.FXObservable import griffon.transform.Observable @@ -33,7 +36,7 @@ class MainFrameModel { @Inject @Nonnull GriffonApplication application @Observable boolean coreInitialized = false - @Observable def results = [] + def results = new ConcurrentHashMap<>() @Observable def downloads = [] @Observable def uploads = [] @Observable def shared = [] @@ -69,11 +72,8 @@ class MainFrameModel { } void onUIResultEvent(UIResultEvent e) { - runInsideUIAsync { - results << e - JTable table = builder.getVariable("results-table") - table.model.fireTableDataChanged() - } + MVCGroup resultsGroup = results.get(e.uuid) + resultsGroup?.model.handleResult(e) } void onDownloadStartedEvent(DownloadStartedEvent e) { diff --git a/gui/griffon-app/models/com/muwire/gui/SearchTabModel.groovy b/gui/griffon-app/models/com/muwire/gui/SearchTabModel.groovy new file mode 100644 index 00000000..c518d74a --- /dev/null +++ b/gui/griffon-app/models/com/muwire/gui/SearchTabModel.groovy @@ -0,0 +1,42 @@ +package com.muwire.gui + +import javax.annotation.Nonnull +import javax.inject.Inject +import javax.swing.JTable + +import com.muwire.core.Core +import com.muwire.core.search.UIResultEvent + +import griffon.core.artifact.GriffonModel +import griffon.core.mvc.MVCGroup +import griffon.inject.MVCMember +import griffon.transform.Observable +import griffon.metadata.ArtifactProviderFor + +@ArtifactProviderFor(GriffonModel) +class SearchTabModel { + @MVCMember @Nonnull + FactoryBuilderSupport builder + + Core core + String uuid + def results = [] + + + void mvcGroupInit(Map args) { + core = mvcGroup.parentGroup.model.core + mvcGroup.parentGroup.model.results[UUID.fromString(uuid)] = mvcGroup + } + + void mvcGroupDestroy() { + mvcGroup.parentGroup.model.results.remove(uuid) + } + + void handleResult(UIResultEvent e) { + runInsideUIAsync { + results << e + JTable table = builder.getVariable("results-table") + table.model.fireTableDataChanged() + } + } +} \ No newline at end of file diff --git a/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy b/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy index 9e995e2e..ea00a12f 100644 --- a/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy +++ b/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy @@ -67,18 +67,7 @@ class MainFrameView { continuousLayout : true, constraints : BorderLayout.CENTER) { panel (constraints : JSplitPane.TOP) { borderLayout() - scrollPane (constraints : BorderLayout.CENTER){ - table(id : "results-table") { - tableModel(list: model.results) { - closureColumn(header: "Name", type: String, read : {row -> row.name}) - closureColumn(header: "Size", preferredWidth: 150, type: Long, read : {row -> row.size}) - closureColumn(header: "Sender", type: String, read : {row -> row.sender.getHumanReadableName()}) - closureColumn(header: "Trust", type: String, read : {row -> - model.core.trustService.getLevel(row.sender.destination) - }) - } - } - } + tabbedPane(id : "result-tabs", constraints: BorderLayout.CENTER) panel(constraints : BorderLayout.SOUTH) { button(text : "Download", downloadAction) button(text : "Trust", trustAction) diff --git a/gui/griffon-app/views/com/muwire/gui/SearchTabView.groovy b/gui/griffon-app/views/com/muwire/gui/SearchTabView.groovy new file mode 100644 index 00000000..2d62f8fa --- /dev/null +++ b/gui/griffon-app/views/com/muwire/gui/SearchTabView.groovy @@ -0,0 +1,68 @@ +package com.muwire.gui + +import griffon.core.artifact.GriffonView +import griffon.core.mvc.MVCGroup +import griffon.inject.MVCMember +import griffon.metadata.ArtifactProviderFor +import javax.swing.SwingConstants + +import java.awt.BorderLayout + +import javax.annotation.Nonnull + +@ArtifactProviderFor(GriffonView) +class SearchTabView { + @MVCMember @Nonnull + FactoryBuilderSupport builder + @MVCMember @Nonnull + SearchTabModel model + + def pane + def parent + def searchTerms + + void initUI() { + builder.with { + def pane = scrollPane { + table(id : "results-table") { + tableModel(list: model.results) { + closureColumn(header: "Name", type: String, read : {row -> row.name}) + closureColumn(header: "Size", preferredWidth: 150, type: Long, read : {row -> row.size}) + closureColumn(header: "Sender", type: String, read : {row -> row.sender.getHumanReadableName()}) + closureColumn(header: "Trust", type: String, read : {row -> + model.core.trustService.getLevel(row.sender.destination) + }) + } + } + } + this.pane = pane + } + } + + void mvcGroupInit(Map args) { + searchTerms = args["search-terms"] + parent = mvcGroup.parentGroup.view.builder.getVariable("result-tabs") + parent.addTab(searchTerms, pane) + int index = parent.indexOfTab(searchTerms) + + def tabPanel + builder.with { + tabPanel = panel { + borderLayout() + panel { + label(text : searchTerms, constraints : BorderLayout.CENTER) + } + button(text : "x", preferredSize : [17,17], constraints : BorderLayout.EAST, // TODO: in osx is probably WEST + actionPerformed : closeTab ) + } + } + + parent.setTabComponentAt(index, tabPanel) + } + + def closeTab = { + int index = parent.indexOfTab(searchTerms) + parent.removeTabAt(index) + mvcGroup.destroy() + } +} \ No newline at end of file diff --git a/gui/src/integration-test/groovy/com/muwire/gui/SearchTabIntegrationTest.groovy b/gui/src/integration-test/groovy/com/muwire/gui/SearchTabIntegrationTest.groovy new file mode 100644 index 00000000..b1012598 --- /dev/null +++ b/gui/src/integration-test/groovy/com/muwire/gui/SearchTabIntegrationTest.groovy @@ -0,0 +1,25 @@ +package com.muwire.gui + +import griffon.core.test.GriffonFestRule +import org.fest.swing.fixture.FrameFixture +import org.junit.Rule +import org.junit.Test + +import static org.junit.Assert.fail + +class SearchTabIntegrationTest { + static { + System.setProperty('griffon.swing.edt.violations.check', 'true') + System.setProperty('griffon.swing.edt.hang.monitor', 'true') + } + + @Rule + public final GriffonFestRule fest = new GriffonFestRule() + + private FrameFixture window + + @Test + void smokeTest() { + fail('Not implemented yet!') + } +} diff --git a/gui/src/test/groovy/com/muwire/gui/SearchTabControllerTest.groovy b/gui/src/test/groovy/com/muwire/gui/SearchTabControllerTest.groovy new file mode 100644 index 00000000..079d4160 --- /dev/null +++ b/gui/src/test/groovy/com/muwire/gui/SearchTabControllerTest.groovy @@ -0,0 +1,21 @@ +package com.muwire.gui + +import griffon.core.test.GriffonUnitRule +import griffon.core.test.TestFor +import org.junit.Rule +import org.junit.Test + +import static org.junit.Assert.fail + +@TestFor(SearchTabController) +class SearchTabControllerTest { + private SearchTabController controller + + @Rule + public final GriffonUnitRule griffon = new GriffonUnitRule() + + @Test + void smokeTest() { + fail('Not yet implemented!') + } +} \ No newline at end of file