Files
muwire/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy
2019-11-07 05:02:53 +00:00

1111 lines
56 KiB
Groovy

package com.muwire.gui
import griffon.core.GriffonApplication
import griffon.core.artifact.GriffonView
import griffon.core.env.Metadata
import griffon.inject.MVCMember
import griffon.metadata.ArtifactProviderFor
import net.i2p.data.Base64
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
import javax.swing.JMenuItem
import javax.swing.JPopupMenu
import javax.swing.JSplitPane
import javax.swing.JTable
import javax.swing.JTree
import javax.swing.ListSelectionModel
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
import javax.swing.tree.TreeNode
import javax.swing.tree.TreePath
import com.muwire.core.Constants
import com.muwire.core.Core
import com.muwire.core.MuWireSettings
import com.muwire.core.SharedFile
import com.muwire.core.download.Downloader
import com.muwire.core.files.FileSharedEvent
import com.muwire.core.trust.RemoteTrustList
import java.awt.BorderLayout
import java.awt.CardLayout
import java.awt.FlowLayout
import java.awt.GridBagConstraints
import java.awt.GridBagLayout
import java.awt.Insets
import java.awt.Rectangle
import java.awt.Toolkit
import java.awt.datatransfer.DataFlavor
import java.awt.datatransfer.StringSelection
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import java.awt.event.WindowAdapter
import java.awt.event.WindowEvent
import java.nio.charset.StandardCharsets
import javax.annotation.Nonnull
import javax.inject.Inject
@ArtifactProviderFor(GriffonView)
class MainFrameView {
@MVCMember @Nonnull
FactoryBuilderSupport builder
@MVCMember @Nonnull
MainFrameModel model
@Inject @Nonnull GriffonApplication application
@Inject Metadata metadata
def downloadsTable
def lastDownloadSortEvent
def lastUploadsSortEvent
def lastSharedSortEvent
def trustTablesSortEvents = [:]
def expansionListener = new TreeExpansions()
UISettings settings
void initUI() {
settings = application.context.get("ui-settings")
int rowHeight = application.context.get("row-height")
builder.with {
application(size : [1024,768], id: 'main-frame',
locationRelativeTo : null,
defaultCloseOperation : JFrame.DO_NOTHING_ON_CLOSE,
title: application.configuration['application.title'] + " " +
metadata["application.version"] + " revision " + metadata["build.revision"],
iconImage: imageIcon('/MuWire-48x48.png').image,
iconImages: [imageIcon('/MuWire-48x48.png').image,
imageIcon('/MuWire-32x32.png').image,
imageIcon('/MuWire-16x16.png').image],
pack : false,
visible : bind { model.coreInitialized }) {
menuBar {
menu (text : "File") {
menuItem("Exit", actionPerformed : {closeApplication()})
}
menu (text : "Options") {
menuItem("Configuration", actionPerformed : {
def params = [:]
params['core'] = application.context.get("core")
params['settings'] = params['core'].muOptions
params['uiSettings'] = settings
mvcGroup.createMVCGroup("Options", params)
})
}
menu (text : "Status") {
menuItem("MuWire", actionPerformed : {mvcGroup.createMVCGroup("mu-wire-status")})
MuWireSettings muSettings = application.context.get("muwire-settings")
menuItem("I2P", enabled : bind {model.routerPresent}, actionPerformed: {mvcGroup.createMVCGroup("i-2-p-status")})
menuItem("System", actionPerformed : {mvcGroup.createMVCGroup("system-status")})
}
menu (text : "Tools") {
menuItem("Content Control", actionPerformed : {
def env = [:]
env["core"] = model.core
mvcGroup.createMVCGroup("content-panel", env)
})
menuItem("Advanced Sharing", actionPerformed : {
def env = [:]
env["core"] = model.core
mvcGroup.createMVCGroup("advanced-sharing",env)
})
menuItem("Certificates", actionPerformed : {
def env = [:]
env['core'] = model.core
mvcGroup.createMVCGroup("certificate-control",env)
})
}
}
borderLayout()
panel (border: etchedBorder(), constraints : BorderLayout.NORTH) {
borderLayout()
panel (constraints: BorderLayout.WEST) {
gridLayout(rows:1, cols: 2)
button(text: "Searches", enabled : bind{model.searchesPaneButtonEnabled},actionPerformed : showSearchWindow)
button(text: "Downloads", enabled : bind{model.downloadsPaneButtonEnabled}, actionPerformed : showDownloadsWindow)
button(text: "Uploads", enabled : bind{model.uploadsPaneButtonEnabled}, actionPerformed : showUploadsWindow)
if (settings.showMonitor)
button(text: "Monitor", enabled: bind{model.monitorPaneButtonEnabled},actionPerformed : showMonitorWindow)
button(text: "Trust", enabled:bind{model.trustPaneButtonEnabled},actionPerformed : showTrustWindow)
}
panel(id: "top-panel", constraints: BorderLayout.CENTER) {
cardLayout()
label(constraints : "top-connect-panel",
text : " MuWire is connecting, please wait. You will be able to search soon.") // TODO: real padding
panel(constraints : "top-search-panel") {
borderLayout()
panel(constraints: BorderLayout.CENTER) {
borderLayout()
label(" Enter search here:", constraints: BorderLayout.WEST) // TODO: fix this
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) {
button(text: "Search", searchAction)
button(text : "", icon : imageIcon("/close_tab.png"), clearSearchAction)
}
}
}
}
panel (id: "cards-panel", constraints : BorderLayout.CENTER) {
cardLayout()
panel (id : "search window", constraints : "search window") {
cardLayout()
panel (constraints : "tabs-panel") {
borderLayout()
tabbedPane(id : "result-tabs", constraints: BorderLayout.CENTER)
}
panel(constraints : "restore session") {
borderLayout()
panel (constraints : BorderLayout.CENTER) {
gridBagLayout()
label(text : "Saved Tabs:", constraints : gbc(gridx : 0, gridy : 0))
scrollPane (constraints : gbc(gridx : 0, gridy : 1)) {
list(items : new ArrayList(settings.openTabs))
}
button(text : "Restore Session", constraints : gbc(gridx :0, gridy : 2), restoreSessionAction)
}
}
}
panel (constraints: "downloads window") {
gridLayout(rows : 1, cols : 1)
splitPane(orientation: JSplitPane.VERTICAL_SPLIT, continuousLayout : true, dividerLocation: 500 ) {
panel {
borderLayout()
scrollPane (constraints : BorderLayout.CENTER) {
downloadsTable = table(id : "downloads-table", autoCreateRowSorter : true, rowHeight : rowHeight) {
tableModel(list: model.downloads) {
closureColumn(header: "Name", preferredWidth: 300, type: String, read : {row -> row.downloader.file.getName()})
closureColumn(header: "Status", preferredWidth: 50, type: String, read : {row -> row.downloader.getCurrentState().toString()})
closureColumn(header: "Progress", preferredWidth: 70, type: Downloader, read: { row -> row.downloader })
closureColumn(header: "Speed", preferredWidth: 50, type:String, read :{row ->
DataHelper.formatSize2Decimal(row.downloader.speed(), false) + "B/sec"
})
closureColumn(header : "ETA", preferredWidth : 50, type:String, read :{ row ->
def speed = row.downloader.speed()
if (speed == 0)
return "Unknown"
else {
def remaining = (row.downloader.nPieces - row.downloader.donePieces()) * row.downloader.pieceSize / speed
return DataHelper.formatDuration(remaining.toLong() * 1000)
}
})
}
}
}
panel (constraints : BorderLayout.SOUTH) {
button(text: "Pause", enabled : bind {model.pauseButtonEnabled}, pauseAction)
button(text: bind { model.resumeButtonText }, enabled : bind {model.retryButtonEnabled}, resumeAction)
button(text: "Cancel", enabled : bind {model.cancelButtonEnabled }, cancelAction)
button(text: "Clear Done", enabled : bind {model.clearButtonEnabled}, clearAction)
}
}
panel {
borderLayout()
panel(constraints : BorderLayout.NORTH) {
label(text : "Download Details")
}
scrollPane(constraints : BorderLayout.CENTER) {
panel (id : "download-details-panel") {
cardLayout()
panel (constraints : "select-download") {
label(text : "Select a download to view details")
}
panel(constraints : "download-selected") {
gridBagLayout()
label(text : "Download Location:", constraints : gbc(gridx:0, gridy:0))
label(text : bind {model.downloader?.file?.getAbsolutePath()},
constraints: gbc(gridx:1, gridy:0, gridwidth: 2, insets : [0,0,0,20]))
label(text : "Piece Size", constraints : gbc(gridx: 0, gridy:1))
label(text : bind {model.downloader?.pieceSize}, constraints : gbc(gridx:1, gridy:1))
label(text : "Known Sources:", constraints : gbc(gridx:3, gridy: 0))
label(text : bind {model.downloader?.activeWorkers?.size()}, constraints : gbc(gridx:4, gridy:0, insets : [0,0,0,20]))
label(text : "Active Sources:", constraints : gbc(gridx:3, gridy:1))
label(text : bind {model.downloader?.activeWorkers()}, constraints : gbc(gridx:4, gridy:1, insets : [0,0,0,20]))
label(text : "Total Pieces:", constraints : gbc(gridx:5, gridy: 0))
label(text : bind {model.downloader?.nPieces}, constraints : gbc(gridx:6, gridy:0, insets : [0,0,0,20]))
label(text : "Done Pieces:", constraints: gbc(gridx:5, gridy: 1))
label(text : bind {model.downloader?.donePieces()}, constraints : gbc(gridx:6, gridy:1, insets : [0,0,0,20]))
}
}
}
}
}
}
panel (constraints: "uploads window"){
gridLayout(cols : 1, rows : 2)
panel {
borderLayout()
panel (constraints : BorderLayout.NORTH) {
label(text : bind {
if (model.hashingFile == null) {
"You can drag-and-drop files and directories here"
} else {
"hashing: " + model.hashingFile.getAbsolutePath() + " (" + DataHelper.formatSize2Decimal(model.hashingFile.length(), false).toString() + "B)"
}
})
}
panel (border : etchedBorder(), constraints : BorderLayout.CENTER) {
borderLayout()
panel (id : "shared-files-panel", constraints : BorderLayout.CENTER){
cardLayout()
panel (constraints : "shared files table") {
borderLayout()
scrollPane(constraints : BorderLayout.CENTER) {
table(id : "shared-files-table", autoCreateRowSorter: true, rowHeight : rowHeight) {
tableModel(list : model.shared) {
closureColumn(header : "Name", preferredWidth : 500, type : String, read : {row -> row.getCachedPath()})
closureColumn(header : "Size", preferredWidth : 50, type : Long, read : {row -> row.getCachedLength() })
closureColumn(header : "Comments", preferredWidth : 50, type : Boolean, read : {it.getComment() != null})
closureColumn(header : "Certified", preferredWidth : 50, type : Boolean, read : {
Core core = application.context.get("core")
core.certificateManager.hasLocalCertificate(it.getInfoHash())
})
closureColumn(header : "Search Hits", preferredWidth: 50, type : Integer, read : {it.getHits()})
closureColumn(header : "Downloaders", preferredWidth: 50, type : Integer, read : {it.getDownloaders().size()})
}
}
}
}
panel (constraints : "shared files tree") {
borderLayout()
scrollPane(constraints : BorderLayout.CENTER) {
def jtree = new JTree(model.sharedTree)
jtree.setCellRenderer(new SharedTreeRenderer())
tree(id : "shared-files-tree", rowHeight : rowHeight, rootVisible : false, expandsSelectedPaths: true, jtree)
}
}
}
}
panel (constraints : BorderLayout.SOUTH) {
gridLayout(rows:1, cols:3)
panel {
buttonGroup(id : "sharedViewType")
radioButton(text : "Tree", selected : true, buttonGroup : sharedViewType, actionPerformed : showSharedFilesTree)
radioButton(text : "Table", selected : false, buttonGroup : sharedViewType, actionPerformed : showSharedFilesTable)
}
panel {
button(text : "Share files", actionPerformed : shareFiles)
button(text : "Add Comment", enabled : bind {model.addCommentButtonEnabled}, addCommentAction)
button(text : "Certify", enabled : bind {model.addCommentButtonEnabled}, issueCertificateAction)
}
panel {
panel {
label("Shared:")
label(text : bind {model.loadedFiles}, id : "shared-files-count")
}
}
}
}
panel (border : etchedBorder()) {
borderLayout()
panel (constraints : BorderLayout.NORTH){
label("Uploads")
}
scrollPane (constraints : BorderLayout.CENTER) {
table(id : "uploads-table", autoCreateRowSorter: true, rowHeight : rowHeight) {
tableModel(list : model.uploads) {
closureColumn(header : "Name", type : String, read : {row -> row.uploader.getName() })
closureColumn(header : "Progress", type : String, read : { row ->
int percent = row.uploader.getProgress()
"$percent% of piece".toString()
})
closureColumn(header : "Downloader", type : String, read : { row ->
row.uploader.getDownloader()
})
closureColumn(header : "Remote Pieces", type : String, read : { row ->
int pieces = row.uploader.getTotalPieces()
int done = row.uploader.getDonePieces()
if (row.uploader.getProgress() == 100)
done++
int percent = -1
if ( pieces != 0 ) {
percent = (done * 100) / pieces
}
long size = row.uploader.getTotalSize()
String totalSize = ""
if (size >= 0 ) {
totalSize = " of " + DataHelper.formatSize2Decimal(size, false) + "B"
}
String.format("%02d", percent) + "% ${totalSize} ($done/$pieces pcs)".toString()
})
closureColumn(header : "Speed", type : String, read : { row ->
int speed = row.uploader.speed()
DataHelper.formatSize2Decimal(speed, false) + "B/sec"
})
}
}
}
panel (constraints : BorderLayout.SOUTH) {
button(text : "Clear Finished Uploads", clearUploadsAction)
}
}
}
panel (constraints: "monitor window") {
gridLayout(rows : 1, cols : 2)
panel {
borderLayout()
panel (constraints : BorderLayout.NORTH){
label("Connections")
}
scrollPane(constraints : BorderLayout.CENTER) {
table(id : "connections-table", rowHeight : rowHeight) {
tableModel(list : model.connectionList) {
closureColumn(header : "Destination", preferredWidth: 250, type: String, read : { row -> row.destination.toBase32() })
closureColumn(header : "Direction", preferredWidth: 20, type: String, read : { row ->
if (row.incoming)
return "In"
else
return "Out"
})
}
}
}
}
panel {
borderLayout()
panel (constraints : BorderLayout.NORTH){
label("Incoming searches")
}
scrollPane(constraints : BorderLayout.CENTER) {
table(id : "searches-table", rowHeight : rowHeight) {
tableModel(list : model.searches) {
closureColumn(header : "Keywords", type : String, read : {
sanitized = it.search.replace('<', ' ')
sanitized
})
closureColumn(header : "From", type : String, read : {
if (it.originator != null) {
return it.originator.getHumanReadableName()
} else {
return it.replyTo.toBase32()
}
})
closureColumn(header : "Count", type : String, read : {
it.count.toString()
})
closureColumn(header : "Timestamp", type : String, read : {
String.format("%02d", it.timestamp.get(Calendar.HOUR_OF_DAY)) + ":" +
String.format("%02d", it.timestamp.get(Calendar.MINUTE)) + ":" +
String.format("%02d", it.timestamp.get(Calendar.SECOND))
})
}
}
}
}
}
panel(constraints : "trust window") {
gridLayout(rows : 2, cols : 1)
panel {
gridLayout(rows: 1, cols :2)
panel (border : etchedBorder()){
borderLayout()
scrollPane(constraints : BorderLayout.CENTER) {
table(id : "trusted-table", autoCreateRowSorter : true, rowHeight : rowHeight) {
tableModel(list : model.trusted) {
closureColumn(header : "Trusted Users", type : String, read : { it.getHumanReadableName() } )
}
}
}
panel (constraints : BorderLayout.SOUTH) {
gridBagLayout()
button(text : "Subscribe", enabled : bind {model.subscribeButtonEnabled}, constraints : gbc(gridx: 0, gridy : 0), subscribeAction)
button(text : "Mark Neutral", enabled : bind {model.markNeutralFromTrustedButtonEnabled}, constraints : gbc(gridx: 1, gridy: 0), markNeutralFromTrustedAction)
button(text : "Mark Distrusted", enabled : bind {model.markDistrustedButtonEnabled}, constraints : gbc(gridx: 2, gridy:0), markDistrustedAction)
}
}
panel (border : etchedBorder()){
borderLayout()
scrollPane(constraints : BorderLayout.CENTER) {
table(id : "distrusted-table", autoCreateRowSorter : true, rowHeight : rowHeight) {
tableModel(list : model.distrusted) {
closureColumn(header: "Distrusted Users", type : String, read : { it.getHumanReadableName() } )
}
}
}
panel(constraints : BorderLayout.SOUTH) {
gridBagLayout()
button(text: "Mark Neutral", enabled : bind {model.markNeutralFromDistrustedButtonEnabled}, constraints: gbc(gridx: 0, gridy: 0), markNeutralFromDistrustedAction)
button(text: "Mark Trusted", enabled : bind {model.markTrustedButtonEnabled}, constraints : gbc(gridx: 1, gridy : 0), markTrustedAction)
}
}
}
panel {
borderLayout()
panel (constraints : BorderLayout.NORTH){
label(text : "Trust List Subscriptions")
}
scrollPane(constraints : BorderLayout.CENTER) {
table(id : "subscription-table", autoCreateRowSorter : true, rowHeight : rowHeight) {
tableModel(list : model.subscriptions) {
closureColumn(header : "Name", preferredWidth: 200, type: String, read : {it.persona.getHumanReadableName()})
closureColumn(header : "Trusted", preferredWidth : 20, type: Integer, read : {it.good.size()})
closureColumn(header : "Distrusted", preferredWidth: 20, type: Integer, read : {it.bad.size()})
closureColumn(header : "Status", preferredWidth: 30, type: String, read : {it.status.toString()})
closureColumn(header : "Last Updated", preferredWidth: 200, type : String, read : {
if (it.timestamp == 0)
return "Never"
else
return String.valueOf(new Date(it.timestamp))
})
}
}
}
panel(constraints : BorderLayout.SOUTH) {
button(text : "Review", enabled : bind {model.reviewButtonEnabled}, reviewAction)
button(text : "Update", enabled : bind {model.updateButtonEnabled}, updateAction)
button(text : "Unsubscribe", enabled : bind {model.unsubscribeButtonEnabled}, unsubscribeAction)
}
}
}
}
panel (border: etchedBorder(), constraints : BorderLayout.SOUTH) {
borderLayout()
label(text : bind {model.me}, constraints: BorderLayout.CENTER)
panel (constraints : BorderLayout.EAST) {
label("Connections:")
label(text : bind {model.connections})
}
}
}
}
}
void mvcGroupInit(Map<String, String> args) {
def mainFrame = builder.getVariable("main-frame")
mainFrame.setTransferHandler(new TransferHandler() {
public boolean canImport(TransferHandler.TransferSupport support) {
return support.isDataFlavorSupported(DataFlavor.javaFileListFlavor)
}
public boolean importData(TransferHandler.TransferSupport support) {
def files = support.getTransferable().getTransferData(DataFlavor.javaFileListFlavor)
files.each {
model.core.eventBus.publish(new FileSharedEvent(file : it))
}
showUploadsWindow.call()
true
}
})
mainFrame.addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent e) {
if (application.getContext().get("tray-icon")) {
if (settings.closeWarning) {
runInsideUIAsync {
Map<String, Object> args2 = new HashMap<>()
args2.put("settings", settings)
args2.put("home", model.core.home)
mvcGroup.createMVCGroup("close-warning", "Close Warning", args2)
}
} else if (settings.exitOnClose)
closeApplication()
} else {
closeApplication()
}
}})
// 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)
selectionModel.addListSelectionListener({
def downloadDetailsPanel = builder.getVariable("download-details-panel")
int selectedRow = selectedDownloaderRow()
if (selectedRow < 0) {
model.cancelButtonEnabled = false
model.retryButtonEnabled = false
model.pauseButtonEnabled = false
model.downloader = null
downloadDetailsPanel.getLayout().show(downloadDetailsPanel,"select-download")
return
}
def downloader = model.downloads[selectedRow]?.downloader
if (downloader == null)
return
model.downloader = downloader
downloadDetailsPanel.getLayout().show(downloadDetailsPanel,"download-selected")
switch(downloader.getCurrentState()) {
case Downloader.DownloadState.CONNECTING :
case Downloader.DownloadState.DOWNLOADING :
case Downloader.DownloadState.HASHLIST:
model.cancelButtonEnabled = true
model.pauseButtonEnabled = true
model.retryButtonEnabled = false
break
case Downloader.DownloadState.FAILED:
model.cancelButtonEnabled = true
model.retryButtonEnabled = true
model.resumeButtonText = "Retry"
model.pauseButtonEnabled = false
break
case Downloader.DownloadState.PAUSED:
model.cancelButtonEnabled = true
model.retryButtonEnabled = true
model.resumeButtonText = "Resume"
model.pauseButtonEnabled = false
break
default:
model.cancelButtonEnabled = false
model.retryButtonEnabled = false
model.pauseButtonEnabled = false
}
})
def centerRenderer = new DefaultTableCellRenderer()
centerRenderer.setHorizontalAlignment(JLabel.CENTER)
downloadsTable.setDefaultRenderer(Integer.class, centerRenderer)
downloadsTable.setDefaultRenderer(Downloader.class, new DownloadProgressRenderer())
downloadsTable.rowSorter.addRowSorterListener({evt -> lastDownloadSortEvent = evt})
downloadsTable.rowSorter.setSortsOnUpdates(true)
downloadsTable.rowSorter.setComparator(2, new DownloaderComparator())
downloadsTable.addMouseListener(new MouseAdapter() {
@Override
public void mouseReleased(MouseEvent e) {
if (e.isPopupTrigger())
showDownloadsMenu(e)
}
@Override
public void mousePressed(MouseEvent e) {
if (e.isPopupTrigger())
showDownloadsMenu(e)
}
})
// shared files menu
JPopupMenu sharedFilesMenu = new JPopupMenu()
JMenuItem copyHashToClipboard = new JMenuItem("Copy hash to clipboard")
copyHashToClipboard.addActionListener({mvcGroup.view.copyHashToClipboard()})
sharedFilesMenu.add(copyHashToClipboard)
JMenuItem unshareSelectedFiles = new JMenuItem("Unshare selected files")
unshareSelectedFiles.addActionListener({mvcGroup.controller.unshareSelectedFile()})
sharedFilesMenu.add(unshareSelectedFiles)
JMenuItem commentSelectedFiles = new JMenuItem("Comment selected files")
commentSelectedFiles.addActionListener({mvcGroup.controller.addComment()})
sharedFilesMenu.add(commentSelectedFiles)
JMenuItem certifySelectedFiles = new JMenuItem("Certify selected files")
certifySelectedFiles.addActionListener({mvcGroup.controller.issueCertificate()})
sharedFilesMenu.add(certifySelectedFiles)
def sharedFilesMouseListener = new MouseAdapter() {
@Override
public void mouseReleased(MouseEvent e) {
if (e.isPopupTrigger())
showPopupMenu(sharedFilesMenu, e)
}
@Override
public void mousePressed(MouseEvent e) {
if (e.isPopupTrigger())
showPopupMenu(sharedFilesMenu, e)
}
}
// shared files table and tree
def sharedFilesTable = builder.getVariable("shared-files-table")
sharedFilesTable.columnModel.getColumn(1).setCellRenderer(new SizeRenderer())
sharedFilesTable.rowSorter.addRowSorterListener({evt -> lastSharedSortEvent = evt})
sharedFilesTable.rowSorter.setSortsOnUpdates(true)
sharedFilesTable.addMouseListener(sharedFilesMouseListener)
selectionModel = sharedFilesTable.getSelectionModel()
selectionModel.addListSelectionListener({
def selectedFiles = selectedSharedFiles()
if (selectedFiles == null || selectedFiles.isEmpty())
return
model.addCommentButtonEnabled = true
})
def sharedFilesTree = builder.getVariable("shared-files-tree")
sharedFilesTree.addMouseListener(sharedFilesMouseListener)
sharedFilesTree.addTreeSelectionListener({
def selectedNode = sharedFilesTree.getLastSelectedPathComponent()
model.addCommentButtonEnabled = selectedNode != null
})
sharedFilesTree.addTreeExpansionListener(expansionListener)
// uploadsTable
def uploadsTable = builder.getVariable("uploads-table")
uploadsTable.rowSorter.addRowSorterListener({evt -> lastUploadsSortEvent = evt})
uploadsTable.rowSorter.setSortsOnUpdates(true)
selectionModel = uploadsTable.getSelectionModel()
selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
JPopupMenu uploadsTableMenu = new JPopupMenu()
JMenuItem showInLibrary = new JMenuItem("Show in library")
showInLibrary.addActionListener({mvcGroup.controller.showInLibrary()})
uploadsTableMenu.add(showInLibrary)
uploadsTable.addMouseListener(new MouseAdapter() {
public void mouseReleased(MouseEvent e) {
if (e.isPopupTrigger())
showPopupMenu(uploadsTableMenu, e)
}
public void mousePressed(MouseEvent e) {
if (e.isPopupTrigger())
showPopupMenu(uploadsTableMenu, e)
}
})
// searches table
def searchesTable = builder.getVariable("searches-table")
JPopupMenu searchTableMenu = new JPopupMenu()
JMenuItem copySearchToClipboard = new JMenuItem("Copy search to clipboard")
copySearchToClipboard.addActionListener({mvcGroup.view.copySearchToClipboard(searchesTable)})
JMenuItem trustSearcher = new JMenuItem("Trust searcher")
trustSearcher.addActionListener({mvcGroup.controller.trustPersonaFromSearch()})
JMenuItem distrustSearcher = new JMenuItem("Distrust searcher")
distrustSearcher.addActionListener({mvcGroup.controller.distrustPersonaFromSearch()})
searchTableMenu.add(copySearchToClipboard)
searchTableMenu.add(trustSearcher)
searchTableMenu.add(distrustSearcher)
searchesTable.addMouseListener(new MouseAdapter() {
@Override
public void mouseReleased(MouseEvent e) {
if (e.isPopupTrigger())
showPopupMenu(searchTableMenu, e)
}
@Override
public void mousePressed(MouseEvent e) {
if (e.isPopupTrigger())
showPopupMenu(searchTableMenu, e)
}
})
// subscription table
def subscriptionTable = builder.getVariable("subscription-table")
subscriptionTable.setDefaultRenderer(Integer.class, centerRenderer)
subscriptionTable.rowSorter.addRowSorterListener({evt -> trustTablesSortEvents["subscription-table"] = evt})
subscriptionTable.rowSorter.setSortsOnUpdates(true)
selectionModel = subscriptionTable.getSelectionModel()
selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
selectionModel.addListSelectionListener({
int selectedRow = getSelectedTrustTablesRow("subscription-table")
if (selectedRow < 0) {
model.reviewButtonEnabled = false
model.updateButtonEnabled = false
model.unsubscribeButtonEnabled = false
return
}
def trustList = model.subscriptions[selectedRow]
if (trustList == null)
return
switch(trustList.status) {
case RemoteTrustList.Status.NEW:
case RemoteTrustList.Status.UPDATING:
model.reviewButtonEnabled = false
model.updateButtonEnabled = false
model.unsubscribeButtonEnabled = false
break
case RemoteTrustList.Status.UPDATED:
model.reviewButtonEnabled = true
model.updateButtonEnabled = true
model.unsubscribeButtonEnabled = true
break
case RemoteTrustList.Status.UPDATE_FAILED:
model.reviewButtonEnabled = false
model.updateButtonEnabled = true
model.unsubscribeButtonEnabled = true
break
}
})
// trusted table
def trustedTable = builder.getVariable("trusted-table")
trustedTable.rowSorter.addRowSorterListener({evt -> trustTablesSortEvents["trusted-table"] = evt})
trustedTable.rowSorter.setSortsOnUpdates(true)
selectionModel = trustedTable.getSelectionModel()
selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
selectionModel.addListSelectionListener({
int selectedRow = getSelectedTrustTablesRow("trusted-table")
if (selectedRow < 0) {
model.subscribeButtonEnabled = false
model.markDistrustedButtonEnabled = false
model.markNeutralFromTrustedButtonEnabled = false
} else {
model.subscribeButtonEnabled = true
model.markDistrustedButtonEnabled = true
model.markNeutralFromTrustedButtonEnabled = true
}
})
// distrusted table
def distrustedTable = builder.getVariable("distrusted-table")
distrustedTable.rowSorter.addRowSorterListener({evt -> trustTablesSortEvents["distrusted-table"] = evt})
distrustedTable.rowSorter.setSortsOnUpdates(true)
selectionModel = distrustedTable.getSelectionModel()
selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
selectionModel.addListSelectionListener({
int selectedRow = getSelectedTrustTablesRow("distrusted-table")
if (selectedRow < 0) {
model.markTrustedButtonEnabled = false
model.markNeutralFromDistrustedButtonEnabled = false
} else {
model.markTrustedButtonEnabled = true
model.markNeutralFromDistrustedButtonEnabled = true
}
})
// show tree by default
showSharedFilesTree.call()
// show search panel by default
showSearchWindow.call()
}
private static void showPopupMenu(JPopupMenu menu, MouseEvent event) {
menu.show(event.getComponent(), event.getX(), event.getY())
}
def selectedSharedFiles() {
if (!model.treeVisible) {
def sharedFilesTable = builder.getVariable("shared-files-table")
int[] selected = sharedFilesTable.getSelectedRows()
if (selected.length == 0)
return null
List<SharedFile> rv = new ArrayList<>()
if (lastSharedSortEvent != null) {
for (int i = 0; i < selected.length; i ++) {
selected[i] = sharedFilesTable.rowSorter.convertRowIndexToModel(selected[i])
}
}
selected.each {
rv.add(model.shared[it])
}
return rv
} else {
def sharedFilesTree = builder.getVariable("shared-files-tree")
List<SharedFile> rv = new ArrayList<>()
for (TreePath path : sharedFilesTree.getSelectionPaths()) {
getLeafs(path.getLastPathComponent(), rv)
}
return rv
}
}
private static void getLeafs(TreeNode node, List<SharedFile> dest) {
if (node.isLeaf()) {
dest.add(node.getUserObject())
return
}
def children = node.children()
while(children.hasMoreElements()) {
getLeafs(children.nextElement(), dest)
}
}
def copyHashToClipboard() {
def selectedFiles = selectedSharedFiles()
if (selectedFiles == null)
return
String roots = ""
for (Iterator<SharedFile> iterator = selectedFiles.iterator(); iterator.hasNext(); ) {
SharedFile selected = iterator.next()
String root = Base64.encode(selected.infoHash.getRoot())
roots += root
if (iterator.hasNext())
roots += "\n"
}
StringSelection selection = new StringSelection(roots)
def clipboard = Toolkit.getDefaultToolkit().getSystemClipboard()
clipboard.setContents(selection, null)
}
def copySearchToClipboard(JTable searchesTable) {
int selected = searchesTable.getSelectedRow()
if (selected < 0)
return
String search = model.searches[selected].search
StringSelection selection = new StringSelection(search)
def clipboard = Toolkit.getDefaultToolkit().getSystemClipboard()
clipboard.setContents(selection, null)
}
int selectedDownloaderRow() {
def downloadsTable = builder.getVariable("downloads-table")
int selected = downloadsTable.getSelectedRow()
if (selected < 0)
return selected
if (lastDownloadSortEvent != null)
selected = downloadsTable.rowSorter.convertRowIndexToModel(selected)
selected
}
def showDownloadsMenu(MouseEvent e) {
int selected = selectedDownloaderRow()
if (selected < 0)
return
boolean pauseEnabled = false
boolean cancelEnabled = false
boolean retryEnabled = false
String resumeText = "Retry"
Downloader downloader = model.downloads[selected].downloader
switch(downloader.currentState) {
case Downloader.DownloadState.DOWNLOADING:
case Downloader.DownloadState.HASHLIST:
case Downloader.DownloadState.CONNECTING:
pauseEnabled = true
cancelEnabled = true
retryEnabled = false
break
case Downloader.DownloadState.FAILED:
pauseEnabled = false
cancelEnabled = true
retryEnabled = true
break
case Downloader.DownloadState.PAUSED:
pauseEnabled = false
cancelEnabled = true
retryEnabled = true
resumeText = "Resume"
break
default :
pauseEnabled = false
cancelEnabled = false
retryEnabled = false
}
JPopupMenu menu = new JPopupMenu()
JMenuItem copyHashToClipboard = new JMenuItem("Copy hash to clipboard")
copyHashToClipboard.addActionListener({
String hash = Base64.encode(downloader.infoHash.getRoot())
StringSelection selection = new StringSelection(hash)
def clipboard = Toolkit.getDefaultToolkit().getSystemClipboard()
clipboard.setContents(selection, null)
})
menu.add(copyHashToClipboard)
if (pauseEnabled) {
JMenuItem pause = new JMenuItem("Pause")
pause.addActionListener({mvcGroup.controller.pause()})
menu.add(pause)
}
if (cancelEnabled) {
JMenuItem cancel = new JMenuItem("Cancel")
cancel.addActionListener({mvcGroup.controller.cancel()})
menu.add(cancel)
}
if (retryEnabled) {
JMenuItem retry = new JMenuItem(resumeText)
retry.addActionListener({mvcGroup.controller.resume()})
menu.add(retry)
}
showPopupMenu(menu, e)
}
def selectedUploader() {
def uploadsTable = builder.getVariable("uploads-table")
int selectedRow = uploadsTable.getSelectedRow()
if (selectedRow < 0)
return null
if (lastUploadsSortEvent != null)
selectedRow = uploadsTable.rowSorter.convertRowIndexToModel(selectedRow)
model.uploads[selectedRow].uploader
}
void focusOnSharedFile(SharedFile sf) {
if(model.treeVisible) {
def tree = builder.getVariable("shared-files-tree")
def node = model.fileToNode.get(sf)
if (node == null)
return
def path = new TreePath(node.getPath())
tree.setSelectionPath(path)
tree.scrollPathToVisible(path)
} else {
def table = builder.getVariable("shared-files-table")
int row = model.shared.indexOf(sf)
if (row < 0)
return
table.setRowSelectionInterval(row, row)
table.scrollRectToVisible(new Rectangle(table.getCellRect(row, 0, true)))
}
}
void showRestoreOrEmpty() {
def searchWindow = builder.getVariable("search window")
String id
if (!model.sessionRestored && !settings.openTabs.isEmpty())
id = model.connections > 0 ? "restore session" : "tabs-panel"
else
id = "tabs-panel"
searchWindow.getLayout().show(searchWindow, id)
}
def showSearchWindow = {
def cardsPanel = builder.getVariable("cards-panel")
cardsPanel.getLayout().show(cardsPanel, "search window")
showRestoreOrEmpty()
model.searchesPaneButtonEnabled = false
model.downloadsPaneButtonEnabled = true
model.uploadsPaneButtonEnabled = true
model.monitorPaneButtonEnabled = true
model.trustPaneButtonEnabled = true
}
def showDownloadsWindow = {
def cardsPanel = builder.getVariable("cards-panel")
cardsPanel.getLayout().show(cardsPanel, "downloads window")
model.searchesPaneButtonEnabled = true
model.downloadsPaneButtonEnabled = false
model.uploadsPaneButtonEnabled = true
model.monitorPaneButtonEnabled = true
model.trustPaneButtonEnabled = true
}
def showUploadsWindow = {
def cardsPanel = builder.getVariable("cards-panel")
cardsPanel.getLayout().show(cardsPanel, "uploads window")
model.searchesPaneButtonEnabled = true
model.downloadsPaneButtonEnabled = true
model.uploadsPaneButtonEnabled = false
model.monitorPaneButtonEnabled = true
model.trustPaneButtonEnabled = true
}
def showMonitorWindow = {
def cardsPanel = builder.getVariable("cards-panel")
cardsPanel.getLayout().show(cardsPanel,"monitor window")
model.searchesPaneButtonEnabled = true
model.downloadsPaneButtonEnabled = true
model.uploadsPaneButtonEnabled = true
model.monitorPaneButtonEnabled = false
model.trustPaneButtonEnabled = true
}
def showTrustWindow = {
def cardsPanel = builder.getVariable("cards-panel")
cardsPanel.getLayout().show(cardsPanel,"trust window")
model.searchesPaneButtonEnabled = true
model.downloadsPaneButtonEnabled = true
model.uploadsPaneButtonEnabled = true
model.monitorPaneButtonEnabled = true
model.trustPaneButtonEnabled = false
}
def showSharedFilesTable = {
model.treeVisible = false
def cardsPanel = builder.getVariable("shared-files-panel")
cardsPanel.getLayout().show(cardsPanel, "shared files table")
}
def showSharedFilesTree = {
model.treeVisible = true
def cardsPanel = builder.getVariable("shared-files-panel")
cardsPanel.getLayout().show(cardsPanel, "shared files tree")
}
def shareFiles = {
def chooser = new JFileChooser()
chooser.setFileHidingEnabled(!model.core.muOptions.shareHiddenFiles)
chooser.setDialogTitle("Select file to share")
chooser.setFileSelectionMode(JFileChooser.FILES_ONLY)
chooser.setMultiSelectionEnabled(true)
int rv = chooser.showOpenDialog(null)
if (rv == JFileChooser.APPROVE_OPTION) {
chooser.getSelectedFiles().each {
File canonical = it.getCanonicalFile()
model.core.eventBus.publish(new FileSharedEvent(file : canonical))
}
}
}
int getSelectedTrustTablesRow(String tableName) {
def table = builder.getVariable(tableName)
int selectedRow = table.getSelectedRow()
if (selectedRow < 0)
return -1
if (trustTablesSortEvents.get(tableName) != null)
selectedRow = table.rowSorter.convertRowIndexToModel(selectedRow)
selectedRow
}
public void refreshSharedFiles() {
def tree = builder.getVariable("shared-files-tree")
TreePath[] selectedPaths = tree.getSelectionPaths()
Set<TreePath> expanded = new HashSet<>(expansionListener.expandedPaths)
model.sharedTree.nodeStructureChanged(model.treeRoot)
expanded.each { tree.expandPath(it) }
tree.setSelectionPaths(selectedPaths)
builder.getVariable("shared-files-table").model.fireTableDataChanged()
}
private void closeApplication() {
Core core = application.getContext().get("core")
def tabbedPane = builder.getVariable("result-tabs")
settings.openTabs.clear()
int count = tabbedPane.getTabCount()
for (int i = 0; i < count; i++)
settings.openTabs.add(tabbedPane.getTitleAt(i))
File uiPropsFile = new File(core.home, "gui.properties")
uiPropsFile.withOutputStream { settings.write(it) }
def mainFrame = builder.getVariable("main-frame")
mainFrame.setVisible(false)
application.getWindowManager().findWindow("shutdown-window").setVisible(true)
if (core != null) {
Thread t = new Thread({
core.shutdown()
application.shutdown()
}as Runnable)
t.start()
}
}
private static class TreeExpansions implements TreeExpansionListener {
private final Set<TreePath> expandedPaths = new HashSet<>()
@Override
public void treeExpanded(TreeExpansionEvent event) {
expandedPaths.add(event.getPath())
}
@Override
public void treeCollapsed(TreeExpansionEvent event) {
expandedPaths.remove(event.getPath())
}
}
}