Compare commits
30 Commits
muwire-0.6
...
muwire-0.6
Author | SHA1 | Date | |
---|---|---|---|
![]() |
0c40c8f269 | ||
![]() |
681ddb99a2 | ||
![]() |
5dff319746 | ||
![]() |
57c4a00ac6 | ||
![]() |
286a0a8678 | ||
![]() |
17eff7d77f | ||
![]() |
2e22369ce0 | ||
![]() |
15c59b440f | ||
![]() |
8fb015acbf | ||
![]() |
f7b11c90fd | ||
![]() |
df93a35062 | ||
![]() |
ecb19a8412 | ||
![]() |
b1e5b40800 | ||
![]() |
daa3a293f2 | ||
![]() |
907264fc67 | ||
![]() |
c6becb93dc | ||
![]() |
2954bd2f1a | ||
![]() |
35322d2c15 | ||
![]() |
9f6a7eb368 | ||
![]() |
fec81808e5 | ||
![]() |
4db890484d | ||
![]() |
dfd5e06889 | ||
![]() |
71da8e14da | ||
![]() |
7dc37e3e0d | ||
![]() |
3de058a078 | ||
![]() |
4d70c7adce | ||
![]() |
5b41106476 | ||
![]() |
6240b22e66 | ||
![]() |
0e26f5afd7 | ||
![]() |
114bc06dbb |
@@ -4,11 +4,11 @@ MuWire is an easy to use file-sharing program which offers anonymity using [I2P
|
|||||||
|
|
||||||
It is inspired by the LimeWire Gnutella client and developped by a former LimeWire developer.
|
It is inspired by the LimeWire Gnutella client and developped by a former LimeWire developer.
|
||||||
|
|
||||||
The current stable release - 0.6.2 is avaiable for download at https://muwire.com. You can find technical documentation in the "doc" folder.
|
The current stable release - 0.6.5 is avaiable for download at https://muwire.com. You can find technical documentation in the "doc" folder.
|
||||||
|
|
||||||
### Building
|
### Building
|
||||||
|
|
||||||
You need JDK 8 or newer. After installing that and setting up the appropriate paths, just type
|
You need JDK 9 or newer. After installing that and setting up the appropriate paths, just type
|
||||||
|
|
||||||
```
|
```
|
||||||
./gradlew clean assemble
|
./gradlew clean assemble
|
||||||
|
@@ -72,4 +72,27 @@ class BrowseModel {
|
|||||||
void setPercentageLabel(Label percentage) {
|
void setPercentageLabel(Label percentage) {
|
||||||
this.percentage = percentage
|
this.percentage = percentage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void sort(SortType type) {
|
||||||
|
Comparator<UIResultEvent> chosen
|
||||||
|
switch(type) {
|
||||||
|
case SortType.NAME_ASC : chosen = ResultComparators.NAME_ASC; break
|
||||||
|
case SortType.NAME_DESC : chosen = ResultComparators.NAME_DESC; break
|
||||||
|
case SortType.SIZE_ASC : chosen = ResultComparators.SIZE_ASC; break
|
||||||
|
case SortType.SIZE_DESC : chosen = ResultComparators.SIZE_DESC; break
|
||||||
|
}
|
||||||
|
|
||||||
|
List<UIResultEvent> l = new ArrayList<>(rootToResult.values())
|
||||||
|
Collections.sort(l, chosen)
|
||||||
|
|
||||||
|
int rowCount = model.getRowCount()
|
||||||
|
rowCount.times { model.removeRow(0) }
|
||||||
|
|
||||||
|
l.each { e ->
|
||||||
|
String size = DataHelper.formatSize2Decimal(e.size, false) + "B"
|
||||||
|
String infoHash = Base64.encode(e.infohash.getRoot())
|
||||||
|
String comment = String.valueOf(e.comment != null)
|
||||||
|
model.addRow(e.name, size, infoHash, comment, e.certificates)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -58,11 +58,17 @@ class BrowseView extends BasicWindow {
|
|||||||
}
|
}
|
||||||
contentPanel.addComponent(table, layoutData)
|
contentPanel.addComponent(table, layoutData)
|
||||||
|
|
||||||
|
Panel buttonsPanel = new Panel()
|
||||||
|
buttonsPanel.setLayoutManager(new GridLayout(2))
|
||||||
|
Button sortButton = new Button("Sort...", {sort()})
|
||||||
Button closeButton = new Button("Close",{
|
Button closeButton = new Button("Close",{
|
||||||
model.unregister()
|
model.unregister()
|
||||||
close()
|
close()
|
||||||
})
|
})
|
||||||
contentPanel.addComponent(closeButton, layoutData)
|
buttonsPanel.addComponent(sortButton, layoutData)
|
||||||
|
buttonsPanel.addComponent(closeButton, layoutData)
|
||||||
|
|
||||||
|
contentPanel.addComponent(buttonsPanel, layoutData)
|
||||||
setComponent(contentPanel)
|
setComponent(contentPanel)
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -120,4 +126,11 @@ class BrowseView extends BasicWindow {
|
|||||||
ViewCertificatesView view = new ViewCertificatesView(model, textGUI, core, terminalSize)
|
ViewCertificatesView view = new ViewCertificatesView(model, textGUI, core, terminalSize)
|
||||||
textGUI.addWindowAndWait(view)
|
textGUI.addWindowAndWait(view)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void sort() {
|
||||||
|
SortPrompt prompt = new SortPrompt(textGUI)
|
||||||
|
SortType type = prompt.prompt()
|
||||||
|
if (type != null)
|
||||||
|
model.sort(type)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,88 @@
|
|||||||
|
package com.muwire.clilanterna
|
||||||
|
|
||||||
|
import com.googlecode.lanterna.gui2.TextBox
|
||||||
|
import com.googlecode.lanterna.gui2.TextGUIThread
|
||||||
|
import com.muwire.core.Core
|
||||||
|
import com.muwire.core.Persona
|
||||||
|
import com.muwire.core.chat.ChatConnectionEvent
|
||||||
|
import com.muwire.core.chat.ChatLink
|
||||||
|
import com.muwire.core.chat.ChatMessageEvent
|
||||||
|
import com.muwire.core.chat.UIConnectChatEvent
|
||||||
|
|
||||||
|
import net.i2p.data.DataHelper
|
||||||
|
|
||||||
|
class ChatConsoleModel {
|
||||||
|
private final Core core
|
||||||
|
private final TextGUIThread guiThread
|
||||||
|
|
||||||
|
volatile ChatLink link
|
||||||
|
volatile Thread poller
|
||||||
|
volatile boolean running
|
||||||
|
|
||||||
|
volatile TextBox textBox
|
||||||
|
|
||||||
|
|
||||||
|
ChatConsoleModel(Core core, TextGUIThread guiThread) {
|
||||||
|
this.core = core
|
||||||
|
this.guiThread = guiThread
|
||||||
|
}
|
||||||
|
|
||||||
|
void start() {
|
||||||
|
if (running)
|
||||||
|
return
|
||||||
|
running = true
|
||||||
|
core.chatServer.start()
|
||||||
|
core.eventBus.with {
|
||||||
|
register(ChatConnectionEvent.class, this)
|
||||||
|
publish(new UIConnectChatEvent(host : core.me))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onChatConnectionEvent(ChatConnectionEvent e) {
|
||||||
|
if (e.persona != core.me)
|
||||||
|
return // can't really happen
|
||||||
|
|
||||||
|
link = e.connection
|
||||||
|
poller = new Thread({eventLoop()} as Runnable)
|
||||||
|
poller.setDaemon(true)
|
||||||
|
poller.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
void stop() {
|
||||||
|
if (!running)
|
||||||
|
return
|
||||||
|
running = false
|
||||||
|
core.chatServer.stop()
|
||||||
|
poller?.interrupt()
|
||||||
|
link = null
|
||||||
|
}
|
||||||
|
|
||||||
|
private void eventLoop() {
|
||||||
|
Thread.sleep(1000)
|
||||||
|
while(running) {
|
||||||
|
ChatLink link = this.link
|
||||||
|
if (link == null || !link.isUp()) {
|
||||||
|
Thread.sleep(100)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
Object event = link.nextEvent()
|
||||||
|
if (event instanceof ChatMessageEvent)
|
||||||
|
handleChatMessage(event)
|
||||||
|
else if (event instanceof Persona)
|
||||||
|
handleLeave(event)
|
||||||
|
else
|
||||||
|
throw new IllegalArgumentException("unknown event type $event")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleChatMessage(ChatMessageEvent e) {
|
||||||
|
String text = DataHelper.formatTime(e.timestamp)+" <"+e.sender.getHumanReadableName()+ "> ["+
|
||||||
|
e.room+"] "+e.payload
|
||||||
|
guiThread.invokeLater({textBox.addLine(text)})
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleLeave(Persona p) {
|
||||||
|
guiThread.invokeLater({textBox.addLine(p.getHumanReadableName()+ " disconnected")})
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,101 @@
|
|||||||
|
package com.muwire.clilanterna
|
||||||
|
|
||||||
|
import com.googlecode.lanterna.TerminalSize
|
||||||
|
import com.googlecode.lanterna.gui2.BasicWindow
|
||||||
|
import com.googlecode.lanterna.gui2.Button
|
||||||
|
import com.googlecode.lanterna.gui2.GridLayout
|
||||||
|
import com.googlecode.lanterna.gui2.GridLayout.Alignment
|
||||||
|
import com.googlecode.lanterna.gui2.Label
|
||||||
|
import com.googlecode.lanterna.gui2.LayoutData
|
||||||
|
import com.googlecode.lanterna.gui2.Panel
|
||||||
|
import com.googlecode.lanterna.gui2.TextBox
|
||||||
|
import com.googlecode.lanterna.gui2.TextGUI
|
||||||
|
import com.googlecode.lanterna.gui2.Window
|
||||||
|
import com.muwire.core.Core
|
||||||
|
import com.muwire.core.chat.ChatConnection
|
||||||
|
import com.muwire.core.chat.ChatMessageEvent
|
||||||
|
import com.muwire.core.chat.ChatServer
|
||||||
|
|
||||||
|
import net.i2p.data.DataHelper
|
||||||
|
|
||||||
|
class ChatConsoleView extends BasicWindow {
|
||||||
|
private final TextGUI textGUI
|
||||||
|
private final ChatConsoleModel model
|
||||||
|
private final Core core
|
||||||
|
|
||||||
|
private final LayoutData layoutData = GridLayout.createLayoutData(Alignment.CENTER, Alignment.CENTER, true, false)
|
||||||
|
|
||||||
|
private final TextBox textBox
|
||||||
|
private final TextBox sayField
|
||||||
|
private final TextBox roomField
|
||||||
|
|
||||||
|
ChatConsoleView(Core core, ChatConsoleModel model, TextGUI textGUI, TerminalSize terminalSize) {
|
||||||
|
super("Chat Server Console")
|
||||||
|
this.core = core
|
||||||
|
this.model = model
|
||||||
|
this.textGUI = textGUI
|
||||||
|
this.textBox = new TextBox(terminalSize,"", TextBox.Style.MULTI_LINE)
|
||||||
|
model.textBox = textBox
|
||||||
|
model.start()
|
||||||
|
this.sayField = new TextBox("", TextBox.Style.SINGLE_LINE)
|
||||||
|
this.roomField = new TextBox("__CONSOLE__", TextBox.Style.SINGLE_LINE)
|
||||||
|
|
||||||
|
|
||||||
|
setHints([Window.Hint.EXPANDED])
|
||||||
|
|
||||||
|
Panel contentPanel = new Panel()
|
||||||
|
contentPanel.setLayoutManager(new GridLayout(1))
|
||||||
|
contentPanel.addComponent(textBox, layoutData)
|
||||||
|
|
||||||
|
Panel inputPanel = new Panel()
|
||||||
|
inputPanel.with {
|
||||||
|
setLayoutManager(new GridLayout(2))
|
||||||
|
addComponent(new Label("Say something here"), layoutData)
|
||||||
|
addComponent(sayField, layoutData)
|
||||||
|
addComponent(new Label("In room:"), layoutData)
|
||||||
|
addComponent(roomField, layoutData)
|
||||||
|
}
|
||||||
|
contentPanel.addComponent(inputPanel, layoutData)
|
||||||
|
|
||||||
|
Panel bottomPanel = new Panel()
|
||||||
|
bottomPanel.setLayoutManager(new GridLayout(4))
|
||||||
|
|
||||||
|
Button sayButton = new Button("Say",{say()})
|
||||||
|
Button startButton = new Button("Start Server",{model.start()})
|
||||||
|
Button stopButton = new Button("Stop Server", {model.stop()})
|
||||||
|
Button closeButton = new Button("Close",{close()})
|
||||||
|
|
||||||
|
bottomPanel.with {
|
||||||
|
addComponent(sayButton, layoutData)
|
||||||
|
addComponent(startButton, layoutData)
|
||||||
|
addComponent(stopButton, layoutData)
|
||||||
|
addComponent(closeButton, layoutData)
|
||||||
|
}
|
||||||
|
contentPanel.addComponent(bottomPanel, layoutData)
|
||||||
|
setComponent(contentPanel)
|
||||||
|
}
|
||||||
|
|
||||||
|
private void say() {
|
||||||
|
String command = sayField.getText()
|
||||||
|
sayField.setText("")
|
||||||
|
String room = roomField.getText()
|
||||||
|
|
||||||
|
UUID uuid = UUID.randomUUID()
|
||||||
|
long now = System.currentTimeMillis()
|
||||||
|
|
||||||
|
String toAppend = DataHelper.formatTime(now) + " <" + core.me.getHumanReadableName() + "> [$room] " + command
|
||||||
|
textBox.addLine(toAppend)
|
||||||
|
|
||||||
|
byte[] sig = ChatConnection.sign(uuid, now, room, command, core.me, core.me, core.spk)
|
||||||
|
|
||||||
|
def event = new ChatMessageEvent( uuid : uuid,
|
||||||
|
payload : command,
|
||||||
|
sender : core.me,
|
||||||
|
host : core.me,
|
||||||
|
room : room,
|
||||||
|
chatTime : now,
|
||||||
|
sig : sig
|
||||||
|
)
|
||||||
|
core.eventBus.publish(event)
|
||||||
|
}
|
||||||
|
}
|
@@ -32,7 +32,7 @@ import com.muwire.core.UILoadedEvent
|
|||||||
import com.muwire.core.files.AllFilesLoadedEvent
|
import com.muwire.core.files.AllFilesLoadedEvent
|
||||||
|
|
||||||
class CliLanterna {
|
class CliLanterna {
|
||||||
private static final String MW_VERSION = "0.6.4"
|
private static final String MW_VERSION = "0.6.6"
|
||||||
|
|
||||||
private static volatile Core core
|
private static volatile Core core
|
||||||
|
|
||||||
|
@@ -78,4 +78,32 @@ class FilesModel {
|
|||||||
model.addRow(new SharedFileWrapper(it), DataHelper.formatSize2(size, false)+"B", comment, certified, hits, downloaders)
|
model.addRow(new SharedFileWrapper(it), DataHelper.formatSize2(size, false)+"B", comment, certified, hits, downloaders)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void sort(SortType type) {
|
||||||
|
Comparator<SharedFile> chosen
|
||||||
|
switch(type) {
|
||||||
|
case SortType.NAME_ASC : chosen = NAME_ASC; break
|
||||||
|
case SortType.NAME_DESC : chosen = NAME_DESC; break
|
||||||
|
case SortType.SIZE_ASC : chosen = SIZE_ASC; break
|
||||||
|
case SortType.SIZE_DESC : chosen = SIZE_DESC; break
|
||||||
|
}
|
||||||
|
|
||||||
|
Collections.sort(sharedFiles, chosen)
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Comparator<SharedFile> NAME_ASC = new Comparator<SharedFile>() {
|
||||||
|
public int compare(SharedFile a, SharedFile b) {
|
||||||
|
a.getFile().getName().compareTo(b.getFile().getName())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Comparator<SharedFile> NAME_DESC = NAME_ASC.reversed()
|
||||||
|
|
||||||
|
private static final Comparator<SharedFile> SIZE_ASC = new Comparator<SharedFile>() {
|
||||||
|
public int compare(SharedFile a, SharedFile b) {
|
||||||
|
Long.compare(a.getCachedLength(), b.getCachedLength())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Comparator<SharedFile> SIZE_DESC = SIZE_ASC.reversed()
|
||||||
}
|
}
|
||||||
|
@@ -51,17 +51,19 @@ class FilesView extends BasicWindow {
|
|||||||
contentPanel.addComponent(table, layoutData)
|
contentPanel.addComponent(table, layoutData)
|
||||||
|
|
||||||
Panel buttonsPanel = new Panel()
|
Panel buttonsPanel = new Panel()
|
||||||
buttonsPanel.setLayoutManager(new GridLayout(4))
|
buttonsPanel.setLayoutManager(new GridLayout(5))
|
||||||
|
|
||||||
Button shareFile = new Button("Share File", {shareFile()})
|
Button shareFile = new Button("Share File", {shareFile()})
|
||||||
Button shareDirectory = new Button("Share Directory", {shareDirectory()})
|
Button shareDirectory = new Button("Share Directory", {shareDirectory()})
|
||||||
Button unshareDirectory = new Button("Unshare Directory",{unshareDirectory()})
|
Button unshareDirectory = new Button("Unshare Directory",{unshareDirectory()})
|
||||||
|
Button sort = new Button("Sort...",{sort()})
|
||||||
Button close = new Button("Close", {close()})
|
Button close = new Button("Close", {close()})
|
||||||
|
|
||||||
buttonsPanel.with {
|
buttonsPanel.with {
|
||||||
addComponent(shareFile, layoutData)
|
addComponent(shareFile, layoutData)
|
||||||
addComponent(shareDirectory, layoutData)
|
addComponent(shareDirectory, layoutData)
|
||||||
addComponent(unshareDirectory, layoutData)
|
addComponent(unshareDirectory, layoutData)
|
||||||
|
addComponent(sort, layoutData)
|
||||||
addComponent(close, layoutData)
|
addComponent(close, layoutData)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,4 +136,11 @@ class FilesView extends BasicWindow {
|
|||||||
core.eventBus.publish(new DirectoryUnsharedEvent(directory : directory))
|
core.eventBus.publish(new DirectoryUnsharedEvent(directory : directory))
|
||||||
MessageDialog.showMessageDialog(textGUI, "Directory Unshared", directory.getName()+" has been unshared", MessageDialogButton.OK)
|
MessageDialog.showMessageDialog(textGUI, "Directory Unshared", directory.getName()+" has been unshared", MessageDialogButton.OK)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void sort() {
|
||||||
|
SortPrompt prompt = new SortPrompt(textGUI)
|
||||||
|
SortType type = prompt.prompt()
|
||||||
|
if (type != null)
|
||||||
|
model.sort(type)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -44,6 +44,7 @@ class MainWindowView extends BasicWindow {
|
|||||||
private final UploadsModel uploadsModel
|
private final UploadsModel uploadsModel
|
||||||
private final FilesModel filesModel
|
private final FilesModel filesModel
|
||||||
private final TrustModel trustModel
|
private final TrustModel trustModel
|
||||||
|
private final ChatConsoleModel chatModel
|
||||||
|
|
||||||
private final Label connectionCount, incoming, outgoing
|
private final Label connectionCount, incoming, outgoing
|
||||||
private final Label known, failing, hopeless
|
private final Label known, failing, hopeless
|
||||||
@@ -63,6 +64,9 @@ class MainWindowView extends BasicWindow {
|
|||||||
uploadsModel = new UploadsModel(textGUI.getGUIThread(), core, props)
|
uploadsModel = new UploadsModel(textGUI.getGUIThread(), core, props)
|
||||||
filesModel = new FilesModel(textGUI.getGUIThread(),core)
|
filesModel = new FilesModel(textGUI.getGUIThread(),core)
|
||||||
trustModel = new TrustModel(textGUI.getGUIThread(), core)
|
trustModel = new TrustModel(textGUI.getGUIThread(), core)
|
||||||
|
chatModel = new ChatConsoleModel(core, textGUI.getGUIThread())
|
||||||
|
if (core.muOptions.startChatServer)
|
||||||
|
core.chatServer.start()
|
||||||
|
|
||||||
setHints([Window.Hint.EXPANDED])
|
setHints([Window.Hint.EXPANDED])
|
||||||
Panel contentPanel = new Panel()
|
Panel contentPanel = new Panel()
|
||||||
@@ -74,7 +78,7 @@ class MainWindowView extends BasicWindow {
|
|||||||
Panel buttonsPanel = new Panel()
|
Panel buttonsPanel = new Panel()
|
||||||
contentPanel.addComponent(buttonsPanel, BorderLayout.Location.TOP)
|
contentPanel.addComponent(buttonsPanel, BorderLayout.Location.TOP)
|
||||||
|
|
||||||
GridLayout gridLayout = new GridLayout(7)
|
GridLayout gridLayout = new GridLayout(8)
|
||||||
buttonsPanel.setLayoutManager(gridLayout)
|
buttonsPanel.setLayoutManager(gridLayout)
|
||||||
|
|
||||||
searchTextBox = new TextBox(new TerminalSize(40, 1))
|
searchTextBox = new TextBox(new TerminalSize(40, 1))
|
||||||
@@ -83,6 +87,7 @@ class MainWindowView extends BasicWindow {
|
|||||||
Button uploadsButton = new Button("Uploads", {upload()})
|
Button uploadsButton = new Button("Uploads", {upload()})
|
||||||
Button filesButton = new Button("Files", { files() })
|
Button filesButton = new Button("Files", { files() })
|
||||||
Button trustButton = new Button("Trust", {trust()})
|
Button trustButton = new Button("Trust", {trust()})
|
||||||
|
Button chatButton = new Button("Chat", {chat()})
|
||||||
Button quitButton = new Button("Quit", {close()})
|
Button quitButton = new Button("Quit", {close()})
|
||||||
|
|
||||||
LayoutData layoutData = GridLayout.createLayoutData(Alignment.CENTER, Alignment.CENTER)
|
LayoutData layoutData = GridLayout.createLayoutData(Alignment.CENTER, Alignment.CENTER)
|
||||||
@@ -94,6 +99,7 @@ class MainWindowView extends BasicWindow {
|
|||||||
addComponent(uploadsButton, layoutData)
|
addComponent(uploadsButton, layoutData)
|
||||||
addComponent(filesButton, layoutData)
|
addComponent(filesButton, layoutData)
|
||||||
addComponent(trustButton, layoutData)
|
addComponent(trustButton, layoutData)
|
||||||
|
addComponent(chatButton, layoutData)
|
||||||
addComponent(quitButton, layoutData)
|
addComponent(quitButton, layoutData)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,6 +277,10 @@ class MainWindowView extends BasicWindow {
|
|||||||
textGUI.addWindowAndWait(new TrustView(trustModel, textGUI, core, sizeForTables()))
|
textGUI.addWindowAndWait(new TrustView(trustModel, textGUI, core, sizeForTables()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void chat() {
|
||||||
|
textGUI.addWindowAndWait(new ChatConsoleView(core, chatModel, textGUI, sizeForTables()))
|
||||||
|
}
|
||||||
|
|
||||||
private void refreshStats() {
|
private void refreshStats() {
|
||||||
int inCon = 0
|
int inCon = 0
|
||||||
int outCon = 0
|
int outCon = 0
|
||||||
|
@@ -0,0 +1,21 @@
|
|||||||
|
package com.muwire.clilanterna
|
||||||
|
|
||||||
|
import com.muwire.core.search.UIResultEvent
|
||||||
|
|
||||||
|
class ResultComparators {
|
||||||
|
public static final Comparator<UIResultEvent> NAME_ASC = new Comparator<UIResultEvent>() {
|
||||||
|
public int compare(UIResultEvent a, UIResultEvent b) {
|
||||||
|
a.name.compareTo(b.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Comparator<UIResultEvent> NAME_DESC = NAME_ASC.reversed()
|
||||||
|
|
||||||
|
public static final Comparator<UIResultEvent> SIZE_ASC = new Comparator<UIResultEvent>() {
|
||||||
|
public int compare(UIResultEvent a, UIResultEvent b) {
|
||||||
|
Long.compare(a.size, b.size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Comparator<UIResultEvent> SIZE_DESC = SIZE_ASC.reversed()
|
||||||
|
}
|
@@ -16,6 +16,26 @@ class ResultsModel {
|
|||||||
ResultsModel(UIResultBatchEvent results) {
|
ResultsModel(UIResultBatchEvent results) {
|
||||||
this.results = results
|
this.results = results
|
||||||
model = new TableModel("Name","Size","Hash","Sources","Comment","Certificates")
|
model = new TableModel("Name","Size","Hash","Sources","Comment","Certificates")
|
||||||
|
updateModel()
|
||||||
|
}
|
||||||
|
|
||||||
|
void sort(SortType type) {
|
||||||
|
Comparator<UIResultEvent> chosen
|
||||||
|
switch(type) {
|
||||||
|
case SortType.NAME_ASC : chosen = ResultComparators.NAME_ASC; break
|
||||||
|
case SortType.NAME_DESC : chosen = ResultComparators.NAME_DESC; break
|
||||||
|
case SortType.SIZE_ASC : chosen = ResultComparators.SIZE_ASC; break
|
||||||
|
case SortType.SIZE_DESC : chosen = ResultComparators.SIZE_DESC; break
|
||||||
|
}
|
||||||
|
|
||||||
|
Arrays.sort(results.results, chosen)
|
||||||
|
updateModel()
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateModel() {
|
||||||
|
int rowCount = model.getRowCount()
|
||||||
|
rowCount.times { model.removeRow(0) }
|
||||||
|
|
||||||
results.results.each {
|
results.results.each {
|
||||||
String size = DataHelper.formatSize2Decimal(it.size, false) + "B"
|
String size = DataHelper.formatSize2Decimal(it.size, false) + "B"
|
||||||
String infoHash = Base64.encode(it.infohash.getRoot())
|
String infoHash = Base64.encode(it.infohash.getRoot())
|
||||||
|
@@ -44,8 +44,13 @@ class ResultsView extends BasicWindow {
|
|||||||
table.setVisibleRows(terminalSize.getRows())
|
table.setVisibleRows(terminalSize.getRows())
|
||||||
contentPanel.addComponent(table, GridLayout.createLayoutData(Alignment.CENTER, Alignment.CENTER, true, false))
|
contentPanel.addComponent(table, GridLayout.createLayoutData(Alignment.CENTER, Alignment.CENTER, true, false))
|
||||||
|
|
||||||
|
Panel buttonsPanel = new Panel()
|
||||||
|
buttonsPanel.setLayoutManager(new GridLayout(2))
|
||||||
|
Button sortButton = new Button("Sort...",{sort()})
|
||||||
|
buttonsPanel.addComponent(sortButton)
|
||||||
Button closeButton = new Button("Close", {close()})
|
Button closeButton = new Button("Close", {close()})
|
||||||
contentPanel.addComponent(closeButton, GridLayout.createLayoutData(Alignment.CENTER, Alignment.CENTER, true, false))
|
buttonsPanel.addComponent(closeButton)
|
||||||
|
contentPanel.addComponent(buttonsPanel, GridLayout.createLayoutData(Alignment.CENTER, Alignment.CENTER, true, false))
|
||||||
|
|
||||||
setComponent(contentPanel)
|
setComponent(contentPanel)
|
||||||
closeButton.takeFocus()
|
closeButton.takeFocus()
|
||||||
@@ -109,4 +114,11 @@ class ResultsView extends BasicWindow {
|
|||||||
ViewCertificatesView view = new ViewCertificatesView(model, textGUI, core, terminalSize)
|
ViewCertificatesView view = new ViewCertificatesView(model, textGUI, core, terminalSize)
|
||||||
textGUI.addWindowAndWait(view)
|
textGUI.addWindowAndWait(view)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void sort() {
|
||||||
|
SortPrompt prompt = new SortPrompt(textGUI)
|
||||||
|
SortType type = prompt.prompt()
|
||||||
|
if (type != null)
|
||||||
|
model.sort(type)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,57 @@
|
|||||||
|
package com.muwire.clilanterna
|
||||||
|
|
||||||
|
import com.googlecode.lanterna.gui2.BasicWindow
|
||||||
|
import com.googlecode.lanterna.gui2.Button
|
||||||
|
import com.googlecode.lanterna.gui2.GridLayout
|
||||||
|
import com.googlecode.lanterna.gui2.GridLayout.Alignment
|
||||||
|
import com.googlecode.lanterna.gui2.LayoutData
|
||||||
|
import com.googlecode.lanterna.gui2.Panel
|
||||||
|
import com.googlecode.lanterna.gui2.TextGUI
|
||||||
|
import com.googlecode.lanterna.gui2.Window
|
||||||
|
|
||||||
|
class SortPrompt extends BasicWindow {
|
||||||
|
private final TextGUI textGUI
|
||||||
|
private SortType type
|
||||||
|
SortPrompt(TextGUI textGUI) {
|
||||||
|
super("Select what to sort by")
|
||||||
|
this.textGUI = textGUI
|
||||||
|
}
|
||||||
|
|
||||||
|
SortType prompt() {
|
||||||
|
setHints([Window.Hint.CENTERED])
|
||||||
|
Panel contentPanel = new Panel()
|
||||||
|
contentPanel.setLayoutManager(new GridLayout(5))
|
||||||
|
|
||||||
|
LayoutData layoutData = GridLayout.createLayoutData(Alignment.CENTER, Alignment.CENTER)
|
||||||
|
|
||||||
|
Button nameAsc = new Button("Name (ascending)",{
|
||||||
|
type = SortType.NAME_ASC
|
||||||
|
close()
|
||||||
|
})
|
||||||
|
Button nameDesc = new Button("Name (descending)",{
|
||||||
|
type = SortType.NAME_DESC
|
||||||
|
close()
|
||||||
|
})
|
||||||
|
Button sizeAsc = new Button("Size (ascending)",{
|
||||||
|
type = SortType.SIZE_ASC
|
||||||
|
close()
|
||||||
|
})
|
||||||
|
Button sizeDesc = new Button("Size (descending)",{
|
||||||
|
type = SortType.SIZE_DESC
|
||||||
|
close()
|
||||||
|
})
|
||||||
|
Button close = new Button("Cancel",{close()})
|
||||||
|
|
||||||
|
contentPanel.with {
|
||||||
|
addComponent(nameAsc, layoutData)
|
||||||
|
addComponent(nameDesc, layoutData)
|
||||||
|
addComponent(sizeAsc, layoutData)
|
||||||
|
addComponent(sizeDesc, layoutData)
|
||||||
|
addComponent(close, layoutData)
|
||||||
|
}
|
||||||
|
|
||||||
|
setComponent(contentPanel)
|
||||||
|
textGUI.addWindowAndWait(this)
|
||||||
|
type
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,5 @@
|
|||||||
|
package com.muwire.clilanterna;
|
||||||
|
|
||||||
|
public enum SortType {
|
||||||
|
NAME_ASC,NAME_DESC,SIZE_ASC,SIZE_DESC
|
||||||
|
}
|
@@ -436,7 +436,7 @@ public class Core {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Core core = new Core(props, home, "0.6.4")
|
Core core = new Core(props, home, "0.6.6")
|
||||||
core.startServices()
|
core.startServices()
|
||||||
|
|
||||||
// ... at the end, sleep or execute script
|
// ... at the end, sleep or execute script
|
||||||
|
@@ -1,20 +1,24 @@
|
|||||||
package com.muwire.core.chat;
|
package com.muwire.core.chat;
|
||||||
|
|
||||||
enum ChatAction {
|
enum ChatAction {
|
||||||
JOIN(true, false, true),
|
JOIN(true, false, true, false),
|
||||||
LEAVE(false, false, true),
|
LEAVE(false, false, true, false),
|
||||||
SAY(false, false, true),
|
SAY(false, false, true, false),
|
||||||
LIST(true, true, true),
|
LIST(true, true, true, false),
|
||||||
HELP(true, true, true),
|
HELP(true, true, true, false),
|
||||||
INFO(true, true, true),
|
INFO(true, true, true, false),
|
||||||
JOINED(true, true, false);
|
JOINED(true, true, false, false),
|
||||||
|
TRUST(true, false, true, true),
|
||||||
|
DISTRUST(true, false, true, true);
|
||||||
|
|
||||||
final boolean console;
|
final boolean console;
|
||||||
final boolean stateless;
|
final boolean stateless;
|
||||||
final boolean user;
|
final boolean user;
|
||||||
ChatAction(boolean console, boolean stateless, boolean user) {
|
final boolean local;
|
||||||
|
ChatAction(boolean console, boolean stateless, boolean user, boolean local) {
|
||||||
this.console = console;
|
this.console = console;
|
||||||
this.stateless = stateless;
|
this.stateless = stateless;
|
||||||
this.user = user;
|
this.user = user;
|
||||||
|
this.local = local;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
package com.muwire.core.chat
|
package com.muwire.core.chat
|
||||||
|
|
||||||
|
import java.lang.System.Logger.Level
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
import java.util.concurrent.Executor
|
import java.util.concurrent.Executor
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
@@ -28,10 +29,10 @@ class ChatClient implements Closeable {
|
|||||||
private final TrustService trustService
|
private final TrustService trustService
|
||||||
private final MuWireSettings settings
|
private final MuWireSettings settings
|
||||||
|
|
||||||
private volatile ChatConnection connection
|
private ChatConnection connection
|
||||||
private volatile boolean connectInProgress
|
private boolean connectInProgress
|
||||||
private volatile long lastRejectionTime
|
private long lastRejectionTime
|
||||||
private volatile Thread connectThread
|
private Thread connectThread
|
||||||
|
|
||||||
ChatClient(I2PConnector connector, EventBus eventBus, Persona host, Persona me, TrustService trustService,
|
ChatClient(I2PConnector connector, EventBus eventBus, Persona host, Persona me, TrustService trustService,
|
||||||
MuWireSettings settings) {
|
MuWireSettings settings) {
|
||||||
@@ -43,15 +44,19 @@ class ChatClient implements Closeable {
|
|||||||
this.settings = settings
|
this.settings = settings
|
||||||
}
|
}
|
||||||
|
|
||||||
void connectIfNeeded() {
|
synchronized void connectIfNeeded() {
|
||||||
if (connection != null || connectInProgress || (System.currentTimeMillis() - lastRejectionTime < REJECTION_BACKOFF))
|
if (connection != null || connectInProgress || (System.currentTimeMillis() - lastRejectionTime < REJECTION_BACKOFF))
|
||||||
return
|
return
|
||||||
|
connectInProgress = true
|
||||||
CONNECTOR.execute({connect()})
|
CONNECTOR.execute({connect()})
|
||||||
}
|
}
|
||||||
|
|
||||||
private void connect() {
|
private void connect() {
|
||||||
connectInProgress = true
|
synchronized(this) {
|
||||||
|
if (!connectInProgress)
|
||||||
|
return
|
||||||
connectThread = Thread.currentThread()
|
connectThread = Thread.currentThread()
|
||||||
|
}
|
||||||
Endpoint endpoint = null
|
Endpoint endpoint = null
|
||||||
try {
|
try {
|
||||||
eventBus.publish(new ChatConnectionEvent(status : ChatConnectionAttemptStatus.CONNECTING, persona : host))
|
eventBus.publish(new ChatConnectionEvent(status : ChatConnectionAttemptStatus.CONNECTING, persona : host))
|
||||||
@@ -72,8 +77,11 @@ class ChatClient implements Closeable {
|
|||||||
|
|
||||||
if (code == 429) {
|
if (code == 429) {
|
||||||
eventBus.publish(new ChatConnectionEvent(status : ChatConnectionAttemptStatus.REJECTED, persona : host))
|
eventBus.publish(new ChatConnectionEvent(status : ChatConnectionAttemptStatus.REJECTED, persona : host))
|
||||||
|
try { dos.close() } catch (IOException ignore) {}
|
||||||
endpoint.close()
|
endpoint.close()
|
||||||
|
synchronized(this) {
|
||||||
lastRejectionTime = System.currentTimeMillis()
|
lastRejectionTime = System.currentTimeMillis()
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,32 +96,47 @@ class ChatClient implements Closeable {
|
|||||||
if (version != Constants.CHAT_VERSION)
|
if (version != Constants.CHAT_VERSION)
|
||||||
throw new Exception("Unknown chat version $version")
|
throw new Exception("Unknown chat version $version")
|
||||||
|
|
||||||
|
synchronized(this) {
|
||||||
|
if (!connectInProgress)
|
||||||
|
return
|
||||||
connection = new ChatConnection(eventBus, endpoint, host, false, trustService, settings)
|
connection = new ChatConnection(eventBus, endpoint, host, false, trustService, settings)
|
||||||
connection.start()
|
connection.start()
|
||||||
|
}
|
||||||
eventBus.publish(new ChatConnectionEvent(status : ChatConnectionAttemptStatus.SUCCESSFUL, persona : host,
|
eventBus.publish(new ChatConnectionEvent(status : ChatConnectionAttemptStatus.SUCCESSFUL, persona : host,
|
||||||
connection : connection))
|
connection : connection))
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
log.log(java.util.logging.Level.WARNING, "connect failed", e)
|
||||||
eventBus.publish(new ChatConnectionEvent(status : ChatConnectionAttemptStatus.FAILED, persona : host))
|
eventBus.publish(new ChatConnectionEvent(status : ChatConnectionAttemptStatus.FAILED, persona : host))
|
||||||
endpoint?.close()
|
if (endpoint != null) {
|
||||||
|
try {endpoint.getOutputStream().close() } catch (IOException ignore) {}
|
||||||
|
endpoint.close()
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
synchronized(this) {
|
||||||
connectInProgress = false
|
connectInProgress = false
|
||||||
connectThread = null
|
connectThread = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void disconnected() {
|
synchronized void disconnected() {
|
||||||
connectInProgress = false
|
connectInProgress = false
|
||||||
connection = null
|
connection = null
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
synchronized public void close() {
|
||||||
|
connectInProgress = false
|
||||||
connectThread?.interrupt()
|
connectThread?.interrupt()
|
||||||
connection?.close()
|
connection?.close()
|
||||||
eventBus.publish(new ChatConnectionEvent(status : ChatConnectionAttemptStatus.DISCONNECTED, persona : host))
|
eventBus.publish(new ChatConnectionEvent(status : ChatConnectionAttemptStatus.DISCONNECTED, persona : host))
|
||||||
}
|
}
|
||||||
|
|
||||||
void ping() {
|
synchronized void ping() {
|
||||||
connection?.sendPing()
|
connection?.sendPing()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
synchronized void sendChat(ChatMessageEvent e) {
|
||||||
|
connection?.sendChat(e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -109,6 +109,7 @@ class ChatConnection implements ChatLink {
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.log(Level.WARNING,"unhandled exception in reader", e)
|
log.log(Level.WARNING,"unhandled exception in reader", e)
|
||||||
} finally {
|
} finally {
|
||||||
|
try {endpoint.getOutputStream().close()} catch (IOException ignore) {}
|
||||||
close()
|
close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -123,6 +124,7 @@ class ChatConnection implements ChatLink {
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.log(Level.WARNING,"unhandled exception in writer",e)
|
log.log(Level.WARNING,"unhandled exception in writer",e)
|
||||||
} finally {
|
} finally {
|
||||||
|
try {endpoint.getOutputStream().close()} catch (IOException ignore) {}
|
||||||
close()
|
close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -7,4 +7,8 @@ class ChatConnectionEvent extends Event {
|
|||||||
ChatConnectionAttemptStatus status
|
ChatConnectionAttemptStatus status
|
||||||
Persona persona
|
Persona persona
|
||||||
ChatLink connection
|
ChatLink connection
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
super.toString() + " " + persona.getHumanReadableName() + " " + status.toString()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -51,7 +51,7 @@ class ChatManager {
|
|||||||
return
|
return
|
||||||
if (e.sender != me)
|
if (e.sender != me)
|
||||||
return
|
return
|
||||||
clients[e.host]?.connection?.sendChat(e)
|
clients[e.host]?.sendChat(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
void onChatDisconnectionEvent(ChatDisconnectionEvent e) {
|
void onChatDisconnectionEvent(ChatDisconnectionEvent e) {
|
||||||
|
@@ -34,6 +34,7 @@ class ChatServer {
|
|||||||
private final Map<Destination, ChatLink> connections = new ConcurrentHashMap()
|
private final Map<Destination, ChatLink> connections = new ConcurrentHashMap()
|
||||||
private final Map<String, Set<Persona>> rooms = new ConcurrentHashMap<>()
|
private final Map<String, Set<Persona>> rooms = new ConcurrentHashMap<>()
|
||||||
private final Map<Persona, Set<String>> memberships = new ConcurrentHashMap<>()
|
private final Map<Persona, Set<String>> memberships = new ConcurrentHashMap<>()
|
||||||
|
private final Map<String, Persona> shortNames = new ConcurrentHashMap<>()
|
||||||
|
|
||||||
private final AtomicBoolean running = new AtomicBoolean()
|
private final AtomicBoolean running = new AtomicBoolean()
|
||||||
|
|
||||||
@@ -49,9 +50,11 @@ class ChatServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void start() {
|
public void start() {
|
||||||
running.set(true)
|
if (!running.compareAndSet(false, true))
|
||||||
|
return
|
||||||
connections.put(me.destination, LocalChatLink.INSTANCE)
|
connections.put(me.destination, LocalChatLink.INSTANCE)
|
||||||
joinRoom(me, CONSOLE)
|
joinRoom(me, CONSOLE)
|
||||||
|
shortNames.put(me.getHumanReadableName(), me)
|
||||||
echo("/SAY Welcome to my chat server! Type /HELP for list of available commands.",me.destination)
|
echo("/SAY Welcome to my chat server! Type /HELP for list of available commands.",me.destination)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,6 +108,7 @@ class ChatServer {
|
|||||||
ChatConnection connection = new ChatConnection(eventBus, endpoint, client, true, trustService, settings)
|
ChatConnection connection = new ChatConnection(eventBus, endpoint, client, true, trustService, settings)
|
||||||
connections.put(endpoint.destination, connection)
|
connections.put(endpoint.destination, connection)
|
||||||
joinRoom(client, CONSOLE)
|
joinRoom(client, CONSOLE)
|
||||||
|
shortNames.put(client.getHumanReadableName(), client)
|
||||||
connection.start()
|
connection.start()
|
||||||
echo("/SAY Welcome to my chat server! Type /HELP for help on available commands",connection.endpoint.destination)
|
echo("/SAY Welcome to my chat server! Type /HELP for help on available commands",connection.endpoint.destination)
|
||||||
}
|
}
|
||||||
@@ -120,6 +124,7 @@ class ChatServer {
|
|||||||
leaveRoom(e.persona, it)
|
leaveRoom(e.persona, it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
shortNames.remove(e.persona.getHumanReadableName())
|
||||||
connections.each { k, v ->
|
connections.each { k, v ->
|
||||||
v.sendLeave(e.persona)
|
v.sendLeave(e.persona)
|
||||||
}
|
}
|
||||||
@@ -188,6 +193,9 @@ class ChatServer {
|
|||||||
!command.action.user)
|
!command.action.user)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if (command.action.local && e.sender != me)
|
||||||
|
return
|
||||||
|
|
||||||
switch(command.action) {
|
switch(command.action) {
|
||||||
case ChatAction.JOIN : processJoin(command.payload, e); break
|
case ChatAction.JOIN : processJoin(command.payload, e); break
|
||||||
case ChatAction.LEAVE : processLeave(e); break
|
case ChatAction.LEAVE : processLeave(e); break
|
||||||
@@ -195,6 +203,8 @@ class ChatServer {
|
|||||||
case ChatAction.LIST : processList(e.sender.destination); break
|
case ChatAction.LIST : processList(e.sender.destination); break
|
||||||
case ChatAction.INFO : processInfo(e.sender.destination); break
|
case ChatAction.INFO : processInfo(e.sender.destination); break
|
||||||
case ChatAction.HELP : processHelp(e.sender.destination); break
|
case ChatAction.HELP : processHelp(e.sender.destination); break
|
||||||
|
case ChatAction.TRUST : processTrust(command.payload, TrustLevel.TRUSTED); break
|
||||||
|
case ChatAction.DISTRUST : processTrust(command.payload, TrustLevel.DISTRUSTED); break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,12 +274,14 @@ class ChatServer {
|
|||||||
|
|
||||||
private void processHelp(Destination d) {
|
private void processHelp(Destination d) {
|
||||||
String help = """/SAY
|
String help = """/SAY
|
||||||
Available commands: /JOIN /LEAVE /SAY /LIST /INFO /HELP
|
Available commands: /JOIN /LEAVE /SAY /LIST /INFO /TRUST /DISTRUST /HELP
|
||||||
/JOIN <room name> - joins a room, or creates one if it does not exist. You must type this in the console
|
/JOIN <room name> - joins a room, or creates one if it does not exist. You must type this in the console
|
||||||
/LEAVE - leaves a room. You must type this in the room you want to leave
|
/LEAVE - leaves a room. You must type this in the room you want to leave
|
||||||
/SAY - optional, says something in the room you're in
|
/SAY - optional, says something in the room you're in
|
||||||
/LIST - lists the existing rooms on this server. You must type this in the console
|
/LIST - lists the existing rooms on this server. You must type this in the console
|
||||||
/INFO - shows information about this server. You must type this in the console
|
/INFO - shows information about this server. You must type this in the console
|
||||||
|
/TRUST <user> - marks user as trusted. This is only available to the server owner
|
||||||
|
/DISTRUST <user> - marks user as distrusted. This is only available to the server owner
|
||||||
/HELP - prints this help message
|
/HELP - prints this help message
|
||||||
"""
|
"""
|
||||||
echo(help, d)
|
echo(help, d)
|
||||||
@@ -292,6 +304,13 @@ class ChatServer {
|
|||||||
connections[d]?.sendChat(echo)
|
connections[d]?.sendChat(echo)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void processTrust(String shortName, TrustLevel level) {
|
||||||
|
Persona p = shortNames.get(shortName)
|
||||||
|
if (p == null)
|
||||||
|
return
|
||||||
|
eventBus.publish(new TrustEvent(persona : p, level : level))
|
||||||
|
}
|
||||||
|
|
||||||
void stop() {
|
void stop() {
|
||||||
if (running.compareAndSet(true, false)) {
|
if (running.compareAndSet(true, false)) {
|
||||||
connections.each { k, v ->
|
connections.each { k, v ->
|
||||||
|
@@ -7,7 +7,7 @@ class FileTree {
|
|||||||
private final TreeNode root = new TreeNode()
|
private final TreeNode root = new TreeNode()
|
||||||
private final Map<File, TreeNode> fileToNode = new ConcurrentHashMap<>()
|
private final Map<File, TreeNode> fileToNode = new ConcurrentHashMap<>()
|
||||||
|
|
||||||
void add(File file) {
|
synchronized void add(File file) {
|
||||||
List<File> path = new ArrayList<>()
|
List<File> path = new ArrayList<>()
|
||||||
path.add(file)
|
path.add(file)
|
||||||
while (file.getParentFile() != null) {
|
while (file.getParentFile() != null) {
|
||||||
@@ -31,7 +31,7 @@ class FileTree {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean remove(File file) {
|
synchronized boolean remove(File file) {
|
||||||
TreeNode node = fileToNode.remove(file)
|
TreeNode node = fileToNode.remove(file)
|
||||||
if (node == null) {
|
if (node == null) {
|
||||||
return false
|
return false
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
group = com.muwire
|
group = com.muwire
|
||||||
version = 0.6.4
|
version = 0.6.6
|
||||||
i2pVersion = 0.9.43
|
i2pVersion = 0.9.43
|
||||||
groovyVersion = 2.4.15
|
groovyVersion = 2.4.15
|
||||||
slf4jVersion = 1.7.25
|
slf4jVersion = 1.7.25
|
||||||
|
@@ -64,8 +64,11 @@ class BrowseController {
|
|||||||
def selectedResults = view.selectedResults()
|
def selectedResults = view.selectedResults()
|
||||||
if (selectedResults == null || selectedResults.isEmpty())
|
if (selectedResults == null || selectedResults.isEmpty())
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def group = application.mvcGroupManager.getGroups()['MainFrame']
|
||||||
|
|
||||||
selectedResults.removeAll {
|
selectedResults.removeAll {
|
||||||
!mvcGroup.parentGroup.parentGroup.model.canDownload(it.infohash)
|
!group.model.canDownload(it.infohash)
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedResults.each { result ->
|
selectedResults.each { result ->
|
||||||
@@ -74,11 +77,11 @@ class BrowseController {
|
|||||||
result : [result],
|
result : [result],
|
||||||
sources : [model.host.destination],
|
sources : [model.host.destination],
|
||||||
target : file,
|
target : file,
|
||||||
sequential : mvcGroup.parentGroup.view.sequentialDownloadCheckbox.model.isSelected()
|
sequential : view.sequentialDownloadCheckbox.model.isSelected()
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
mvcGroup.parentGroup.parentGroup.view.showDownloadsWindow.call()
|
group.view.showDownloadsWindow.call()
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -71,6 +71,7 @@ class ChatRoomController {
|
|||||||
params['console'] = false
|
params['console'] = false
|
||||||
params['host'] = model.host
|
params['host'] = model.host
|
||||||
params['roomTabName'] = newRoom
|
params['roomTabName'] = newRoom
|
||||||
|
params['chatNotificator'] = view.chatNotificator
|
||||||
|
|
||||||
mvcGroup.parentGroup.createMVCGroup("chat-room", model.host.getHumanReadableName()+"-"+newRoom, params)
|
mvcGroup.parentGroup.createMVCGroup("chat-room", model.host.getHumanReadableName()+"-"+newRoom, params)
|
||||||
}
|
}
|
||||||
@@ -110,6 +111,7 @@ class ChatRoomController {
|
|||||||
params['privateChat'] = true
|
params['privateChat'] = true
|
||||||
params['host'] = model.host
|
params['host'] = model.host
|
||||||
params['roomTabName'] = p.getHumanReadableName()
|
params['roomTabName'] = p.getHumanReadableName()
|
||||||
|
params['chatNotificator'] = view.chatNotificator
|
||||||
|
|
||||||
mvcGroup.parentGroup.createMVCGroup("chat-room", groupId, params)
|
mvcGroup.parentGroup.createMVCGroup("chat-room", groupId, params)
|
||||||
}
|
}
|
||||||
@@ -141,6 +143,17 @@ class ChatRoomController {
|
|||||||
view.refreshMembersTable()
|
view.refreshMembersTable()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void browse() {
|
||||||
|
Persona p = view.getSelectedPersona()
|
||||||
|
if (p == null)
|
||||||
|
return
|
||||||
|
String groupId = p.getHumanReadableName() + "-browse"
|
||||||
|
def params = [:]
|
||||||
|
params['host'] = p
|
||||||
|
params['core'] = model.core
|
||||||
|
mvcGroup.createMVCGroup("browse",groupId,params)
|
||||||
|
}
|
||||||
|
|
||||||
void leaveRoom() {
|
void leaveRoom() {
|
||||||
if (leftRoom)
|
if (leftRoom)
|
||||||
return
|
return
|
||||||
@@ -180,6 +193,8 @@ class ChatRoomController {
|
|||||||
runInsideUIAsync {
|
runInsideUIAsync {
|
||||||
view.roomTextArea.append(toDisplay)
|
view.roomTextArea.append(toDisplay)
|
||||||
trimLines()
|
trimLines()
|
||||||
|
if (!model.console)
|
||||||
|
view.chatNotificator.onMessage(mvcGroup.mvcId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,4 +248,27 @@ class ChatRoomController {
|
|||||||
view.roomTextArea.replaceRange(null, line0Start, line0End)
|
view.roomTextArea.replaceRange(null, line0Start, line0End)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void rejoinRoom() {
|
||||||
|
if (model.room == "Console")
|
||||||
|
return
|
||||||
|
|
||||||
|
model.members.clear()
|
||||||
|
model.members.add(model.core.me)
|
||||||
|
|
||||||
|
UUID uuid = UUID.randomUUID()
|
||||||
|
long now = System.currentTimeMillis()
|
||||||
|
String join = "/JOIN $model.room"
|
||||||
|
byte [] sig = ChatConnection.sign(uuid, now, ChatServer.CONSOLE, join, model.core.me, model.host, model.core.spk)
|
||||||
|
def event = new ChatMessageEvent(
|
||||||
|
uuid : uuid,
|
||||||
|
payload : join,
|
||||||
|
sender : model.core.me,
|
||||||
|
host : model.host,
|
||||||
|
room : ChatServer.CONSOLE,
|
||||||
|
chatTime : now,
|
||||||
|
sig : sig
|
||||||
|
)
|
||||||
|
model.core.eventBus.publish(event)
|
||||||
|
}
|
||||||
}
|
}
|
@@ -15,6 +15,14 @@ class ChatServerController {
|
|||||||
|
|
||||||
@ControllerAction
|
@ControllerAction
|
||||||
void disconnect() {
|
void disconnect() {
|
||||||
|
switch(model.buttonText) {
|
||||||
|
case "Disconnect" :
|
||||||
|
model.buttonText = "Connect"
|
||||||
model.core.eventBus.publish(new UIDisconnectChatEvent(host : model.host))
|
model.core.eventBus.publish(new UIDisconnectChatEvent(host : model.host))
|
||||||
|
break
|
||||||
|
case "Connect" :
|
||||||
|
model.connect()
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -458,6 +458,7 @@ class MainFrameController {
|
|||||||
def params = [:]
|
def params = [:]
|
||||||
params['core'] = model.core
|
params['core'] = model.core
|
||||||
params['host'] = model.core.me
|
params['host'] = model.core.me
|
||||||
|
params['chatNotificator'] = view.chatNotificator
|
||||||
mvcGroup.createMVCGroup("chat-server","local-chat-server", params)
|
mvcGroup.createMVCGroup("chat-server","local-chat-server", params)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -489,8 +490,10 @@ class MainFrameController {
|
|||||||
def params = [:]
|
def params = [:]
|
||||||
params['core'] = model.core
|
params['core'] = model.core
|
||||||
params['host'] = p
|
params['host'] = p
|
||||||
|
params['chatNotificator'] = view.chatNotificator
|
||||||
mvcGroup.createMVCGroup("chat-server", p.getHumanReadableName(), params)
|
mvcGroup.createMVCGroup("chat-server", p.getHumanReadableName(), params)
|
||||||
}
|
} else
|
||||||
|
mvcGroup.getChildrenGroups().get(p.getHumanReadableName()).model.connect()
|
||||||
}
|
}
|
||||||
|
|
||||||
void saveMuWireSettings() {
|
void saveMuWireSettings() {
|
||||||
|
@@ -2,6 +2,8 @@ package com.muwire.gui
|
|||||||
|
|
||||||
import java.util.logging.Level
|
import java.util.logging.Level
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull
|
||||||
|
|
||||||
import com.muwire.core.Core
|
import com.muwire.core.Core
|
||||||
import com.muwire.core.Persona
|
import com.muwire.core.Persona
|
||||||
import com.muwire.core.chat.ChatCommand
|
import com.muwire.core.chat.ChatCommand
|
||||||
@@ -13,6 +15,7 @@ import com.muwire.core.chat.ChatMessageEvent
|
|||||||
import com.muwire.core.chat.UIConnectChatEvent
|
import com.muwire.core.chat.UIConnectChatEvent
|
||||||
|
|
||||||
import griffon.core.artifact.GriffonModel
|
import griffon.core.artifact.GriffonModel
|
||||||
|
import griffon.inject.MVCMember
|
||||||
import griffon.transform.Observable
|
import griffon.transform.Observable
|
||||||
import groovy.util.logging.Log
|
import groovy.util.logging.Log
|
||||||
import griffon.metadata.ArtifactProviderFor
|
import griffon.metadata.ArtifactProviderFor
|
||||||
@@ -20,11 +23,16 @@ import griffon.metadata.ArtifactProviderFor
|
|||||||
@Log
|
@Log
|
||||||
@ArtifactProviderFor(GriffonModel)
|
@ArtifactProviderFor(GriffonModel)
|
||||||
class ChatServerModel {
|
class ChatServerModel {
|
||||||
|
@MVCMember @Nonnull
|
||||||
|
ChatServerView view
|
||||||
|
|
||||||
Persona host
|
Persona host
|
||||||
Core core
|
Core core
|
||||||
|
|
||||||
@Observable boolean disconnectActionEnabled
|
@Observable boolean disconnectActionEnabled
|
||||||
|
@Observable String buttonText = "Disconnect"
|
||||||
@Observable ChatConnectionAttemptStatus status
|
@Observable ChatConnectionAttemptStatus status
|
||||||
|
@Observable boolean sayActionEnabled
|
||||||
|
|
||||||
volatile ChatLink link
|
volatile ChatLink link
|
||||||
volatile Thread poller
|
volatile Thread poller
|
||||||
@@ -32,40 +40,65 @@ class ChatServerModel {
|
|||||||
|
|
||||||
void mvcGroupInit(Map<String, String> params) {
|
void mvcGroupInit(Map<String, String> params) {
|
||||||
disconnectActionEnabled = host != core.me // can't disconnect from myself
|
disconnectActionEnabled = host != core.me // can't disconnect from myself
|
||||||
|
core.eventBus.register(ChatConnectionEvent.class, this)
|
||||||
|
|
||||||
core.eventBus.with {
|
connect()
|
||||||
register(ChatConnectionEvent.class, this)
|
|
||||||
publish(new UIConnectChatEvent(host : host))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void connect() {
|
||||||
|
runInsideUIAsync {
|
||||||
|
buttonText = "Disconnect"
|
||||||
|
}
|
||||||
|
core.eventBus.publish(new UIConnectChatEvent(host : host))
|
||||||
|
}
|
||||||
|
|
||||||
|
void mvcGroupDestroy() {
|
||||||
|
stopPoller()
|
||||||
|
core.eventBus.unregister(ChatConnectionEvent.class, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startPoller() {
|
||||||
|
if (running)
|
||||||
|
return
|
||||||
running = true
|
running = true
|
||||||
poller = new Thread({eventLoop()} as Runnable)
|
poller = new Thread({eventLoop()} as Runnable)
|
||||||
poller.setDaemon(true)
|
poller.setDaemon(true)
|
||||||
poller.start()
|
poller.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
void mvcGroupDestroy() {
|
private void stopPoller() {
|
||||||
running = false
|
running = false
|
||||||
poller?.interrupt()
|
poller?.interrupt()
|
||||||
|
link = null
|
||||||
}
|
}
|
||||||
|
|
||||||
void onChatConnectionEvent(ChatConnectionEvent e) {
|
void onChatConnectionEvent(ChatConnectionEvent e) {
|
||||||
if (e.persona == host) {
|
if (e.persona != host)
|
||||||
|
return
|
||||||
|
|
||||||
runInsideUIAsync {
|
runInsideUIAsync {
|
||||||
status = e.status
|
status = e.status
|
||||||
}
|
sayActionEnabled = status == ChatConnectionAttemptStatus.SUCCESSFUL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (e.status == ChatConnectionAttemptStatus.SUCCESSFUL) {
|
||||||
ChatLink link = e.connection
|
ChatLink link = e.connection
|
||||||
if (link == null)
|
if (link == null)
|
||||||
return
|
return
|
||||||
if (link.getPersona() == host)
|
this.link = e.connection
|
||||||
this.link = link
|
|
||||||
else if (link.getPersona() == null && host == core.me)
|
startPoller()
|
||||||
this.link = link
|
|
||||||
|
mvcGroup.childrenGroups.each {k,v ->
|
||||||
|
v.controller.rejoinRoom()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stopPoller()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void eventLoop() {
|
private void eventLoop() {
|
||||||
|
Thread.sleep(1000)
|
||||||
while(running) {
|
while(running) {
|
||||||
ChatLink link = this.link
|
ChatLink link = this.link
|
||||||
if (link == null || !link.isUp()) {
|
if (link == null || !link.isUp()) {
|
||||||
@@ -106,6 +139,7 @@ class ChatServerModel {
|
|||||||
params['privateChat'] = true
|
params['privateChat'] = true
|
||||||
params['host'] = host
|
params['host'] = host
|
||||||
params['roomTabName'] = e.sender.getHumanReadableName()
|
params['roomTabName'] = e.sender.getHumanReadableName()
|
||||||
|
params['chatNotificator'] = view.chatNotificator
|
||||||
|
|
||||||
mvcGroup.createMVCGroup("chat-room",groupId, params)
|
mvcGroup.createMVCGroup("chat-room",groupId, params)
|
||||||
}
|
}
|
||||||
|
@@ -106,6 +106,8 @@ class MainFrameModel {
|
|||||||
@Observable boolean subscribeButtonEnabled
|
@Observable boolean subscribeButtonEnabled
|
||||||
@Observable boolean markNeutralFromTrustedButtonEnabled
|
@Observable boolean markNeutralFromTrustedButtonEnabled
|
||||||
@Observable boolean markDistrustedButtonEnabled
|
@Observable boolean markDistrustedButtonEnabled
|
||||||
|
@Observable boolean browseFromTrustedButtonEnabled
|
||||||
|
@Observable boolean chatFromTrustedButtonEnabled
|
||||||
@Observable boolean markNeutralFromDistrustedButtonEnabled
|
@Observable boolean markNeutralFromDistrustedButtonEnabled
|
||||||
@Observable boolean markTrustedButtonEnabled
|
@Observable boolean markTrustedButtonEnabled
|
||||||
@Observable boolean reviewButtonEnabled
|
@Observable boolean reviewButtonEnabled
|
||||||
|
@@ -39,6 +39,8 @@ class BrowseView {
|
|||||||
def p
|
def p
|
||||||
def resultsTable
|
def resultsTable
|
||||||
def lastSortEvent
|
def lastSortEvent
|
||||||
|
def sequentialDownloadCheckbox
|
||||||
|
|
||||||
void initUI() {
|
void initUI() {
|
||||||
int rowHeight = application.context.get("row-height")
|
int rowHeight = application.context.get("row-height")
|
||||||
mainFrame = application.windowManager.findWindow("main-frame")
|
mainFrame = application.windowManager.findWindow("main-frame")
|
||||||
@@ -67,6 +69,8 @@ class BrowseView {
|
|||||||
button(text : "View Comment", enabled : bind{model.viewCommentActionEnabled}, viewCommentAction)
|
button(text : "View Comment", enabled : bind{model.viewCommentActionEnabled}, viewCommentAction)
|
||||||
button(text : "View Certificates", enabled : bind{model.viewCertificatesActionEnabled}, viewCertificatesAction)
|
button(text : "View Certificates", enabled : bind{model.viewCertificatesActionEnabled}, viewCertificatesAction)
|
||||||
button(text : "Dismiss", dismissAction)
|
button(text : "Dismiss", dismissAction)
|
||||||
|
label(text : "Download sequentially")
|
||||||
|
sequentialDownloadCheckbox = checkBox()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,8 +110,9 @@ class BrowseView {
|
|||||||
else
|
else
|
||||||
model.viewCommentActionEnabled = false
|
model.viewCommentActionEnabled = false
|
||||||
|
|
||||||
|
def mainFrameGroup = application.mvcGroupManager.getGroups()['MainFrame']
|
||||||
rows.each {
|
rows.each {
|
||||||
downloadActionEnabled &= mvcGroup.parentGroup.parentGroup.model.canDownload(model.results[it].infohash)
|
downloadActionEnabled &= mainFrameGroup.model.canDownload(model.results[it].infohash)
|
||||||
}
|
}
|
||||||
model.downloadActionEnabled = downloadActionEnabled
|
model.downloadActionEnabled = downloadActionEnabled
|
||||||
|
|
||||||
|
@@ -12,6 +12,7 @@ import javax.swing.SwingConstants
|
|||||||
import javax.swing.SpringLayout.Constraints
|
import javax.swing.SpringLayout.Constraints
|
||||||
|
|
||||||
import com.muwire.core.Persona
|
import com.muwire.core.Persona
|
||||||
|
import com.muwire.core.chat.ChatConnectionAttemptStatus
|
||||||
|
|
||||||
import java.awt.BorderLayout
|
import java.awt.BorderLayout
|
||||||
import java.awt.event.MouseAdapter
|
import java.awt.event.MouseAdapter
|
||||||
@@ -28,29 +29,33 @@ class ChatRoomView {
|
|||||||
@MVCMember @Nonnull
|
@MVCMember @Nonnull
|
||||||
ChatRoomController controller
|
ChatRoomController controller
|
||||||
|
|
||||||
|
ChatNotificator chatNotificator
|
||||||
|
|
||||||
def pane
|
def pane
|
||||||
def parent
|
def parent
|
||||||
def sayField
|
def sayField
|
||||||
def roomTextArea
|
def roomTextArea
|
||||||
|
def textScrollPane
|
||||||
def membersTable
|
def membersTable
|
||||||
def lastMembersTableSortEvent
|
def lastMembersTableSortEvent
|
||||||
|
|
||||||
void initUI() {
|
void initUI() {
|
||||||
int rowHeight = application.context.get("row-height")
|
int rowHeight = application.context.get("row-height")
|
||||||
|
def parentModel = mvcGroup.parentGroup.model
|
||||||
if (model.console || model.privateChat) {
|
if (model.console || model.privateChat) {
|
||||||
pane = builder.panel {
|
pane = builder.panel {
|
||||||
borderLayout()
|
borderLayout()
|
||||||
panel(constraints : BorderLayout.CENTER) {
|
panel(constraints : BorderLayout.CENTER) {
|
||||||
gridLayout(rows : 1, cols : 1)
|
gridLayout(rows : 1, cols : 1)
|
||||||
scrollPane {
|
textScrollPane = scrollPane {
|
||||||
roomTextArea = textArea(editable : false, lineWrap : true, wrapStyleWord : true)
|
roomTextArea = textArea(editable : false, lineWrap : true, wrapStyleWord : true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
panel(constraints : BorderLayout.SOUTH) {
|
panel(constraints : BorderLayout.SOUTH) {
|
||||||
borderLayout()
|
borderLayout()
|
||||||
label(text : "Say something here: ", constraints : BorderLayout.WEST)
|
label(text : "Say something here: ", constraints : BorderLayout.WEST)
|
||||||
sayField = textField(actionPerformed : {controller.say()}, constraints : BorderLayout.CENTER)
|
sayField = textField(enabled : bind {parentModel.sayActionEnabled}, actionPerformed : {controller.say()}, constraints : BorderLayout.CENTER)
|
||||||
button(text : "Say", constraints : BorderLayout.EAST, sayAction)
|
button(enabled : bind {parentModel.sayActionEnabled},text : "Say", constraints : BorderLayout.EAST, sayAction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -72,7 +77,7 @@ class ChatRoomView {
|
|||||||
}
|
}
|
||||||
panel {
|
panel {
|
||||||
gridLayout(rows : 1, cols : 1)
|
gridLayout(rows : 1, cols : 1)
|
||||||
scrollPane {
|
textScrollPane = scrollPane {
|
||||||
roomTextArea = textArea(editable : false, lineWrap : true, wrapStyleWord : true)
|
roomTextArea = textArea(editable : false, lineWrap : true, wrapStyleWord : true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -81,12 +86,15 @@ class ChatRoomView {
|
|||||||
panel(constraints : BorderLayout.SOUTH) {
|
panel(constraints : BorderLayout.SOUTH) {
|
||||||
borderLayout()
|
borderLayout()
|
||||||
label(text : "Say something here: ", constraints : BorderLayout.WEST)
|
label(text : "Say something here: ", constraints : BorderLayout.WEST)
|
||||||
sayField = textField(actionPerformed : {controller.say()}, constraints : BorderLayout.CENTER)
|
sayField = textField(enabled : bind {parentModel.sayActionEnabled}, actionPerformed : {controller.say()}, constraints : BorderLayout.CENTER)
|
||||||
button(text : "Say", constraints : BorderLayout.EAST, sayAction)
|
button(enabled : bind {parentModel.sayActionEnabled}, text : "Say", constraints : BorderLayout.EAST, sayAction)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SmartScroller smartScroller = new SmartScroller(textScrollPane)
|
||||||
|
pane.putClientProperty("mvcId", mvcGroup.mvcId)
|
||||||
}
|
}
|
||||||
|
|
||||||
void mvcGroupInit(Map<String,String> args) {
|
void mvcGroupInit(Map<String,String> args) {
|
||||||
@@ -134,6 +142,9 @@ class ChatRoomView {
|
|||||||
JMenuItem privateChat = new JMenuItem("Start Private Chat")
|
JMenuItem privateChat = new JMenuItem("Start Private Chat")
|
||||||
privateChat.addActionListener({controller.privateMessage()})
|
privateChat.addActionListener({controller.privateMessage()})
|
||||||
menu.add(privateChat)
|
menu.add(privateChat)
|
||||||
|
JMenuItem browse = new JMenuItem("Browse")
|
||||||
|
browse.addActionListener({controller.browse()})
|
||||||
|
menu.add(browse)
|
||||||
JMenuItem markTrusted = new JMenuItem("Mark Trusted")
|
JMenuItem markTrusted = new JMenuItem("Mark Trusted")
|
||||||
markTrusted.addActionListener({controller.markTrusted()})
|
markTrusted.addActionListener({controller.markTrusted()})
|
||||||
menu.add(markTrusted)
|
menu.add(markTrusted)
|
||||||
@@ -165,6 +176,7 @@ class ChatRoomView {
|
|||||||
int index = parent.indexOfComponent(pane)
|
int index = parent.indexOfComponent(pane)
|
||||||
parent.removeTabAt(index)
|
parent.removeTabAt(index)
|
||||||
controller.leaveRoom()
|
controller.leaveRoom()
|
||||||
|
chatNotificator.roomClosed(mvcGroup.mvcId)
|
||||||
mvcGroup.destroy()
|
mvcGroup.destroy()
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -20,18 +20,21 @@ class ChatServerView {
|
|||||||
@MVCMember @Nonnull
|
@MVCMember @Nonnull
|
||||||
ChatServerController controller
|
ChatServerController controller
|
||||||
|
|
||||||
|
ChatNotificator chatNotificator
|
||||||
|
|
||||||
def pane
|
def pane
|
||||||
def parent
|
def parent
|
||||||
|
def childPane
|
||||||
|
|
||||||
void initUI() {
|
void initUI() {
|
||||||
pane = builder.panel {
|
pane = builder.panel {
|
||||||
borderLayout()
|
borderLayout()
|
||||||
tabbedPane(id : model.host.getHumanReadableName()+"-chat-rooms", constraints : BorderLayout.CENTER)
|
childPane = tabbedPane(id : model.host.getHumanReadableName()+"-chat-rooms", constraints : BorderLayout.CENTER)
|
||||||
panel(constraints : BorderLayout.SOUTH) {
|
panel(constraints : BorderLayout.SOUTH) {
|
||||||
gridLayout(rows : 1, cols : 3)
|
gridLayout(rows : 1, cols : 3)
|
||||||
panel {}
|
panel {}
|
||||||
panel {
|
panel {
|
||||||
button(text : "Disconnect", enabled : bind {model.disconnectActionEnabled}, disconnectAction)
|
button(text : bind {model.buttonText}, enabled : bind {model.disconnectActionEnabled}, disconnectAction)
|
||||||
}
|
}
|
||||||
panel {
|
panel {
|
||||||
label(text : "Connection Status ")
|
label(text : "Connection Status ")
|
||||||
@@ -39,6 +42,9 @@ class ChatServerView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pane.putClientProperty("mvcId",mvcGroup.mvcId)
|
||||||
|
pane.putClientProperty("childPane", childPane)
|
||||||
|
childPane.addChangeListener({e -> chatNotificator.roomTabChanged(e.getSource())})
|
||||||
}
|
}
|
||||||
|
|
||||||
void mvcGroupInit(Map<String,String> args) {
|
void mvcGroupInit(Map<String,String> args) {
|
||||||
@@ -69,10 +75,12 @@ class ChatServerView {
|
|||||||
params['roomTabName'] = 'Console'
|
params['roomTabName'] = 'Console'
|
||||||
params['console'] = true
|
params['console'] = true
|
||||||
params['host'] = model.host
|
params['host'] = model.host
|
||||||
|
params['chatNotificator'] = chatNotificator
|
||||||
mvcGroup.createMVCGroup("chat-room",model.host.getHumanReadableName()+"-"+ChatServer.CONSOLE, params)
|
mvcGroup.createMVCGroup("chat-room",model.host.getHumanReadableName()+"-"+ChatServer.CONSOLE, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
def closeTab = {
|
def closeTab = {
|
||||||
|
if (model.buttonText == "Disconnect")
|
||||||
controller.disconnect()
|
controller.disconnect()
|
||||||
int index = parent.indexOfComponent(pane)
|
int index = parent.indexOfComponent(pane)
|
||||||
parent.removeTabAt(index)
|
parent.removeTabAt(index)
|
||||||
|
@@ -78,8 +78,10 @@ class MainFrameView {
|
|||||||
|
|
||||||
|
|
||||||
UISettings settings
|
UISettings settings
|
||||||
|
ChatNotificator chatNotificator
|
||||||
|
|
||||||
void initUI() {
|
void initUI() {
|
||||||
|
chatNotificator = new ChatNotificator(application.getMvcGroupManager())
|
||||||
settings = application.context.get("ui-settings")
|
settings = application.context.get("ui-settings")
|
||||||
int rowHeight = application.context.get("row-height")
|
int rowHeight = application.context.get("row-height")
|
||||||
builder.with {
|
builder.with {
|
||||||
@@ -434,8 +436,8 @@ class MainFrameView {
|
|||||||
button(text : "Subscribe", enabled : bind {model.subscribeButtonEnabled}, constraints : gbc(gridx: 0, gridy : 0), subscribeAction)
|
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 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)
|
button(text : "Mark Distrusted", enabled : bind {model.markDistrustedButtonEnabled}, constraints : gbc(gridx: 2, gridy:0), markDistrustedAction)
|
||||||
button(text : "Browse", constraints:gbc(gridx:3, gridy:0), browseFromTrustedAction)
|
button(text : "Browse", enabled : bind{model.browseFromTrustedButtonEnabled}, constraints:gbc(gridx:3, gridy:0), browseFromTrustedAction)
|
||||||
button(text : "Chat", constraints : gbc(gridx:4, gridy:0), chatFromTrustedAction)
|
button(text : "Chat", enabled : bind{model.chatFromTrustedButtonEnabled} ,constraints : gbc(gridx:4, gridy:0), chatFromTrustedAction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
panel (border : etchedBorder()){
|
panel (border : etchedBorder()){
|
||||||
@@ -511,7 +513,9 @@ class MainFrameView {
|
|||||||
public boolean importData(TransferHandler.TransferSupport support) {
|
public boolean importData(TransferHandler.TransferSupport support) {
|
||||||
def files = support.getTransferable().getTransferData(DataFlavor.javaFileListFlavor)
|
def files = support.getTransferable().getTransferData(DataFlavor.javaFileListFlavor)
|
||||||
files.each {
|
files.each {
|
||||||
model.core.eventBus.publish(new FileSharedEvent(file : it))
|
File canonical = it.getCanonicalFile()
|
||||||
|
model.core.fileManager.negativeTree.remove(canonical)
|
||||||
|
model.core.eventBus.publish(new FileSharedEvent(file : canonical))
|
||||||
}
|
}
|
||||||
showUploadsWindow.call()
|
showUploadsWindow.call()
|
||||||
true
|
true
|
||||||
@@ -520,6 +524,7 @@ class MainFrameView {
|
|||||||
|
|
||||||
mainFrame.addWindowListener(new WindowAdapter(){
|
mainFrame.addWindowListener(new WindowAdapter(){
|
||||||
public void windowClosing(WindowEvent e) {
|
public void windowClosing(WindowEvent e) {
|
||||||
|
chatNotificator.mainWindowDeactivated()
|
||||||
if (application.getContext().get("tray-icon")) {
|
if (application.getContext().get("tray-icon")) {
|
||||||
if (settings.closeWarning) {
|
if (settings.closeWarning) {
|
||||||
runInsideUIAsync {
|
runInsideUIAsync {
|
||||||
@@ -533,6 +538,13 @@ class MainFrameView {
|
|||||||
} else {
|
} else {
|
||||||
closeApplication()
|
closeApplication()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
public void windowDeactivated(WindowEvent e) {
|
||||||
|
chatNotificator.mainWindowDeactivated()
|
||||||
|
}
|
||||||
|
public void windowActivated(WindowEvent e) {
|
||||||
|
if (!model.chatPaneButtonEnabled)
|
||||||
|
chatNotificator.mainWindowActivated()
|
||||||
}})
|
}})
|
||||||
|
|
||||||
// search field
|
// search field
|
||||||
@@ -773,10 +785,14 @@ class MainFrameView {
|
|||||||
model.subscribeButtonEnabled = false
|
model.subscribeButtonEnabled = false
|
||||||
model.markDistrustedButtonEnabled = false
|
model.markDistrustedButtonEnabled = false
|
||||||
model.markNeutralFromTrustedButtonEnabled = false
|
model.markNeutralFromTrustedButtonEnabled = false
|
||||||
|
model.chatFromTrustedButtonEnabled = false
|
||||||
|
model.browseFromTrustedButtonEnabled = false
|
||||||
} else {
|
} else {
|
||||||
model.subscribeButtonEnabled = true
|
model.subscribeButtonEnabled = true
|
||||||
model.markDistrustedButtonEnabled = true
|
model.markDistrustedButtonEnabled = true
|
||||||
model.markNeutralFromTrustedButtonEnabled = true
|
model.markNeutralFromTrustedButtonEnabled = true
|
||||||
|
model.chatFromTrustedButtonEnabled = true
|
||||||
|
model.browseFromTrustedButtonEnabled = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -825,6 +841,10 @@ class MainFrameView {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// chat tabs
|
||||||
|
def chatTabbedPane = builder.getVariable("chat-tabs")
|
||||||
|
chatTabbedPane.addChangeListener({e -> chatNotificator.serverTabChanged(e.getSource())})
|
||||||
|
|
||||||
// show tree by default
|
// show tree by default
|
||||||
showSharedFilesTree.call()
|
showSharedFilesTree.call()
|
||||||
|
|
||||||
@@ -1027,6 +1047,7 @@ class MainFrameView {
|
|||||||
model.monitorPaneButtonEnabled = true
|
model.monitorPaneButtonEnabled = true
|
||||||
model.trustPaneButtonEnabled = true
|
model.trustPaneButtonEnabled = true
|
||||||
model.chatPaneButtonEnabled = true
|
model.chatPaneButtonEnabled = true
|
||||||
|
chatNotificator.mainWindowDeactivated()
|
||||||
}
|
}
|
||||||
|
|
||||||
def showDownloadsWindow = {
|
def showDownloadsWindow = {
|
||||||
@@ -1038,6 +1059,7 @@ class MainFrameView {
|
|||||||
model.monitorPaneButtonEnabled = true
|
model.monitorPaneButtonEnabled = true
|
||||||
model.trustPaneButtonEnabled = true
|
model.trustPaneButtonEnabled = true
|
||||||
model.chatPaneButtonEnabled = true
|
model.chatPaneButtonEnabled = true
|
||||||
|
chatNotificator.mainWindowDeactivated()
|
||||||
}
|
}
|
||||||
|
|
||||||
def showUploadsWindow = {
|
def showUploadsWindow = {
|
||||||
@@ -1049,6 +1071,7 @@ class MainFrameView {
|
|||||||
model.monitorPaneButtonEnabled = true
|
model.monitorPaneButtonEnabled = true
|
||||||
model.trustPaneButtonEnabled = true
|
model.trustPaneButtonEnabled = true
|
||||||
model.chatPaneButtonEnabled = true
|
model.chatPaneButtonEnabled = true
|
||||||
|
chatNotificator.mainWindowDeactivated()
|
||||||
}
|
}
|
||||||
|
|
||||||
def showMonitorWindow = {
|
def showMonitorWindow = {
|
||||||
@@ -1060,6 +1083,7 @@ class MainFrameView {
|
|||||||
model.monitorPaneButtonEnabled = false
|
model.monitorPaneButtonEnabled = false
|
||||||
model.trustPaneButtonEnabled = true
|
model.trustPaneButtonEnabled = true
|
||||||
model.chatPaneButtonEnabled = true
|
model.chatPaneButtonEnabled = true
|
||||||
|
chatNotificator.mainWindowDeactivated()
|
||||||
}
|
}
|
||||||
|
|
||||||
def showTrustWindow = {
|
def showTrustWindow = {
|
||||||
@@ -1071,6 +1095,7 @@ class MainFrameView {
|
|||||||
model.monitorPaneButtonEnabled = true
|
model.monitorPaneButtonEnabled = true
|
||||||
model.trustPaneButtonEnabled = false
|
model.trustPaneButtonEnabled = false
|
||||||
model.chatPaneButtonEnabled = true
|
model.chatPaneButtonEnabled = true
|
||||||
|
chatNotificator.mainWindowDeactivated()
|
||||||
}
|
}
|
||||||
|
|
||||||
def showChatWindow = {
|
def showChatWindow = {
|
||||||
@@ -1082,6 +1107,7 @@ class MainFrameView {
|
|||||||
model.monitorPaneButtonEnabled = true
|
model.monitorPaneButtonEnabled = true
|
||||||
model.trustPaneButtonEnabled = true
|
model.trustPaneButtonEnabled = true
|
||||||
model.chatPaneButtonEnabled = false
|
model.chatPaneButtonEnabled = false
|
||||||
|
chatNotificator.mainWindowActivated()
|
||||||
}
|
}
|
||||||
|
|
||||||
def showSharedFilesTable = {
|
def showSharedFilesTable = {
|
||||||
@@ -1106,6 +1132,7 @@ class MainFrameView {
|
|||||||
if (rv == JFileChooser.APPROVE_OPTION) {
|
if (rv == JFileChooser.APPROVE_OPTION) {
|
||||||
chooser.getSelectedFiles().each {
|
chooser.getSelectedFiles().each {
|
||||||
File canonical = it.getCanonicalFile()
|
File canonical = it.getCanonicalFile()
|
||||||
|
model.core.fileManager.negativeTree.remove(canonical)
|
||||||
model.core.eventBus.publish(new FileSharedEvent(file : canonical))
|
model.core.eventBus.publish(new FileSharedEvent(file : canonical))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
91
gui/src/main/groovy/com/muwire/gui/ChatNotificator.groovy
Normal file
91
gui/src/main/groovy/com/muwire/gui/ChatNotificator.groovy
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
package com.muwire.gui
|
||||||
|
|
||||||
|
import java.awt.Taskbar
|
||||||
|
import java.awt.Taskbar.Feature
|
||||||
|
|
||||||
|
import javax.swing.JPanel
|
||||||
|
import javax.swing.JTabbedPane
|
||||||
|
|
||||||
|
import griffon.core.mvc.MVCGroupManager
|
||||||
|
|
||||||
|
class ChatNotificator {
|
||||||
|
|
||||||
|
private final MVCGroupManager groupManager
|
||||||
|
|
||||||
|
private boolean chatInFocus
|
||||||
|
private String currentServerTab
|
||||||
|
private String currentRoomTab
|
||||||
|
|
||||||
|
private final Map<String, Integer> roomsWithMessages = new HashMap<>()
|
||||||
|
|
||||||
|
ChatNotificator(MVCGroupManager groupManager) {
|
||||||
|
this.groupManager = groupManager
|
||||||
|
}
|
||||||
|
|
||||||
|
void serverTabChanged(JTabbedPane source) {
|
||||||
|
JPanel panel = source.getSelectedComponent()
|
||||||
|
String mvcId = panel.getClientProperty("mvcId")
|
||||||
|
def group = groupManager.getGroups().get(mvcId)
|
||||||
|
JTabbedPane childPane = panel.getClientProperty("childPane")
|
||||||
|
JPanel roomPanel = childPane.getSelectedComponent()
|
||||||
|
|
||||||
|
currentServerTab = mvcId
|
||||||
|
currentRoomTab = childPane.getSelectedComponent()?.getClientProperty("mvcId")
|
||||||
|
|
||||||
|
if (currentRoomTab != null) {
|
||||||
|
roomsWithMessages.remove(currentRoomTab)
|
||||||
|
updateBadge()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void roomTabChanged(JTabbedPane source) {
|
||||||
|
JPanel panel = source.getSelectedComponent()
|
||||||
|
currentRoomTab = panel.getClientProperty("mvcId")
|
||||||
|
roomsWithMessages.remove(currentRoomTab)
|
||||||
|
updateBadge()
|
||||||
|
}
|
||||||
|
|
||||||
|
void roomClosed(String mvcId) {
|
||||||
|
roomsWithMessages.remove(mvcId)
|
||||||
|
updateBadge()
|
||||||
|
}
|
||||||
|
|
||||||
|
void mainWindowDeactivated() {
|
||||||
|
chatInFocus = false
|
||||||
|
}
|
||||||
|
|
||||||
|
void mainWindowActivated() {
|
||||||
|
chatInFocus = true
|
||||||
|
if (currentRoomTab != null)
|
||||||
|
roomsWithMessages.remove(currentRoomTab)
|
||||||
|
updateBadge()
|
||||||
|
}
|
||||||
|
|
||||||
|
void onMessage(String roomId) {
|
||||||
|
if (roomId != currentRoomTab || !chatInFocus) {
|
||||||
|
Integer previous = roomsWithMessages[roomId]
|
||||||
|
if (previous == null)
|
||||||
|
roomsWithMessages[roomId] = 1
|
||||||
|
else
|
||||||
|
roomsWithMessages[roomId] = previous + 1
|
||||||
|
}
|
||||||
|
updateBadge()
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateBadge() {
|
||||||
|
if (!Taskbar.isTaskbarSupported())
|
||||||
|
return
|
||||||
|
def taskBar = Taskbar.getTaskbar()
|
||||||
|
if (!taskBar.isSupported(Feature.ICON_BADGE_NUMBER))
|
||||||
|
return
|
||||||
|
if (roomsWithMessages.isEmpty())
|
||||||
|
taskBar.setIconBadge("")
|
||||||
|
else {
|
||||||
|
int total = 0
|
||||||
|
roomsWithMessages.values().each {
|
||||||
|
total += it
|
||||||
|
}
|
||||||
|
taskBar.setIconBadge(String.valueOf(total))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
174
gui/src/main/java/com/muwire/gui/SmartScroller.java
Normal file
174
gui/src/main/java/com/muwire/gui/SmartScroller.java
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
package com.muwire.gui;
|
||||||
|
|
||||||
|
import java.awt.Component;
|
||||||
|
import java.awt.event.*;
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.text.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The SmartScroller will attempt to keep the viewport positioned based on
|
||||||
|
* the users interaction with the scrollbar. The normal behaviour is to keep
|
||||||
|
* the viewport positioned to see new data as it is dynamically added.
|
||||||
|
*
|
||||||
|
* Assuming vertical scrolling and data is added to the bottom:
|
||||||
|
*
|
||||||
|
* - when the viewport is at the bottom and new data is added,
|
||||||
|
* then automatically scroll the viewport to the bottom
|
||||||
|
* - when the viewport is not at the bottom and new data is added,
|
||||||
|
* then do nothing with the viewport
|
||||||
|
*
|
||||||
|
* Assuming vertical scrolling and data is added to the top:
|
||||||
|
*
|
||||||
|
* - when the viewport is at the top and new data is added,
|
||||||
|
* then do nothing with the viewport
|
||||||
|
* - when the viewport is not at the top and new data is added, then adjust
|
||||||
|
* the viewport to the relative position it was at before the data was added
|
||||||
|
*
|
||||||
|
* Similiar logic would apply for horizontal scrolling.
|
||||||
|
*/
|
||||||
|
public class SmartScroller implements AdjustmentListener
|
||||||
|
{
|
||||||
|
public final static int HORIZONTAL = 0;
|
||||||
|
public final static int VERTICAL = 1;
|
||||||
|
|
||||||
|
public final static int START = 0;
|
||||||
|
public final static int END = 1;
|
||||||
|
|
||||||
|
private int viewportPosition;
|
||||||
|
|
||||||
|
private JScrollBar scrollBar;
|
||||||
|
private boolean adjustScrollBar = true;
|
||||||
|
|
||||||
|
private int previousValue = -1;
|
||||||
|
private int previousMaximum = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience constructor.
|
||||||
|
* Scroll direction is VERTICAL and viewport position is at the END.
|
||||||
|
*
|
||||||
|
* @param scrollPane the scroll pane to monitor
|
||||||
|
*/
|
||||||
|
public SmartScroller(JScrollPane scrollPane)
|
||||||
|
{
|
||||||
|
this(scrollPane, VERTICAL, END);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience constructor.
|
||||||
|
* Scroll direction is VERTICAL.
|
||||||
|
*
|
||||||
|
* @param scrollPane the scroll pane to monitor
|
||||||
|
* @param viewportPosition valid values are START and END
|
||||||
|
*/
|
||||||
|
public SmartScroller(JScrollPane scrollPane, int viewportPosition)
|
||||||
|
{
|
||||||
|
this(scrollPane, VERTICAL, viewportPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify how the SmartScroller will function.
|
||||||
|
*
|
||||||
|
* @param scrollPane the scroll pane to monitor
|
||||||
|
* @param scrollDirection indicates which JScrollBar to monitor.
|
||||||
|
* Valid values are HORIZONTAL and VERTICAL.
|
||||||
|
* @param viewportPosition indicates where the viewport will normally be
|
||||||
|
* positioned as data is added.
|
||||||
|
* Valid values are START and END
|
||||||
|
*/
|
||||||
|
public SmartScroller(JScrollPane scrollPane, int scrollDirection, int viewportPosition)
|
||||||
|
{
|
||||||
|
if (scrollDirection != HORIZONTAL
|
||||||
|
&& scrollDirection != VERTICAL)
|
||||||
|
throw new IllegalArgumentException("invalid scroll direction specified");
|
||||||
|
|
||||||
|
if (viewportPosition != START
|
||||||
|
&& viewportPosition != END)
|
||||||
|
throw new IllegalArgumentException("invalid viewport position specified");
|
||||||
|
|
||||||
|
this.viewportPosition = viewportPosition;
|
||||||
|
|
||||||
|
if (scrollDirection == HORIZONTAL)
|
||||||
|
scrollBar = scrollPane.getHorizontalScrollBar();
|
||||||
|
else
|
||||||
|
scrollBar = scrollPane.getVerticalScrollBar();
|
||||||
|
|
||||||
|
scrollBar.addAdjustmentListener( this );
|
||||||
|
|
||||||
|
// Turn off automatic scrolling for text components
|
||||||
|
|
||||||
|
Component view = scrollPane.getViewport().getView();
|
||||||
|
|
||||||
|
if (view instanceof JTextComponent)
|
||||||
|
{
|
||||||
|
JTextComponent textComponent = (JTextComponent)view;
|
||||||
|
DefaultCaret caret = (DefaultCaret)textComponent.getCaret();
|
||||||
|
caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void adjustmentValueChanged(final AdjustmentEvent e)
|
||||||
|
{
|
||||||
|
SwingUtilities.invokeLater(new Runnable()
|
||||||
|
{
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
checkScrollBar(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Analyze every adjustment event to determine when the viewport
|
||||||
|
* needs to be repositioned.
|
||||||
|
*/
|
||||||
|
private void checkScrollBar(AdjustmentEvent e)
|
||||||
|
{
|
||||||
|
// The scroll bar listModel contains information needed to determine
|
||||||
|
// whether the viewport should be repositioned or not.
|
||||||
|
|
||||||
|
JScrollBar scrollBar = (JScrollBar)e.getSource();
|
||||||
|
BoundedRangeModel listModel = scrollBar.getModel();
|
||||||
|
int value = listModel.getValue();
|
||||||
|
int extent = listModel.getExtent();
|
||||||
|
int maximum = listModel.getMaximum();
|
||||||
|
|
||||||
|
boolean valueChanged = previousValue != value;
|
||||||
|
boolean maximumChanged = previousMaximum != maximum;
|
||||||
|
|
||||||
|
// Check if the user has manually repositioned the scrollbar
|
||||||
|
|
||||||
|
if (valueChanged && !maximumChanged)
|
||||||
|
{
|
||||||
|
if (viewportPosition == START)
|
||||||
|
adjustScrollBar = value != 0;
|
||||||
|
else
|
||||||
|
adjustScrollBar = value + extent >= maximum;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset the "value" so we can reposition the viewport and
|
||||||
|
// distinguish between a user scroll and a program scroll.
|
||||||
|
// (ie. valueChanged will be false on a program scroll)
|
||||||
|
|
||||||
|
if (adjustScrollBar && viewportPosition == END)
|
||||||
|
{
|
||||||
|
// Scroll the viewport to the end.
|
||||||
|
scrollBar.removeAdjustmentListener( this );
|
||||||
|
value = maximum - extent;
|
||||||
|
scrollBar.setValue( value );
|
||||||
|
scrollBar.addAdjustmentListener( this );
|
||||||
|
}
|
||||||
|
|
||||||
|
if (adjustScrollBar && viewportPosition == START)
|
||||||
|
{
|
||||||
|
// Keep the viewport at the same relative viewportPosition
|
||||||
|
scrollBar.removeAdjustmentListener( this );
|
||||||
|
value = value + maximum - previousMaximum;
|
||||||
|
scrollBar.setValue( value );
|
||||||
|
scrollBar.addAdjustmentListener( this );
|
||||||
|
}
|
||||||
|
|
||||||
|
previousValue = value;
|
||||||
|
previousMaximum = maximum;
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user