Contact selector dropdown when the user types @

dbus-notify
Zlatin Balevsky 2022-06-10 00:19:37 +01:00
parent 46e0af30df
commit d4f46be3b7
No known key found for this signature in database
GPG Key ID: A72832072D525E41
3 changed files with 206 additions and 39 deletions

View File

@ -2,7 +2,8 @@ package com.muwire.gui
import com.muwire.core.trust.TrustLevel
import com.muwire.gui.chat.ChatEntry
import com.muwire.gui.chat.ChatTextField
import com.muwire.gui.chat.ChatEntryPane
import com.muwire.gui.contacts.POPLabel
import com.muwire.gui.profile.PersonaOrProfile
import com.muwire.gui.profile.PersonaOrProfileCellRenderer
@ -20,23 +21,19 @@ import java.text.SimpleDateFormat
import static com.muwire.gui.Translator.trans
import griffon.inject.MVCMember
import griffon.metadata.ArtifactProviderFor
import net.i2p.data.DataHelper
import javax.swing.JMenuItem
import javax.swing.JPopupMenu
import javax.swing.JSplitPane
import javax.swing.JTextPane
import javax.swing.ListSelectionModel
import javax.swing.SwingConstants
import javax.swing.text.Element
import javax.swing.text.Style
import javax.swing.text.StyleConstants
import javax.swing.text.StyleContext
import javax.swing.text.StyledDocument
import javax.swing.SpringLayout.Constraints
import com.muwire.core.Persona
import com.muwire.core.chat.ChatConnectionAttemptStatus
import java.awt.BorderLayout
import java.awt.Color
@ -60,7 +57,7 @@ class ChatRoomView {
def pane
def parent
ChatTextField sayField
ChatEntryPane sayField
JTextPane roomTextArea
def textScrollPane
def membersTable
@ -68,12 +65,13 @@ class ChatRoomView {
UISettings settings
private static final SimpleDateFormat SDF = new SimpleDateFormat("dd/MM hh:mm:ss")
void initUI() {
settings = application.context.get("ui-settings")
int rowHeight = application.context.get("row-height")
def parentModel = mvcGroup.parentGroup.model
sayField = new ChatTextField()
sayField = new ChatEntryPane(settings, model.members)
if (model.console || model.privateChat) {
pane = builder.panel {

View File

@ -0,0 +1,201 @@
package com.muwire.gui.chat
import com.muwire.gui.UISettings
import com.muwire.gui.contacts.POPLabel
import com.muwire.gui.profile.PersonaOrProfile
import com.muwire.gui.profile.ProfileConstants
import sun.swing.UIAction
import javax.swing.*
import javax.swing.event.MenuKeyEvent
import javax.swing.event.MenuKeyListener
import javax.swing.text.*
import java.awt.*
import java.awt.event.ActionEvent
import java.awt.event.KeyAdapter
import java.awt.event.KeyEvent
import java.util.List
import java.util.stream.Collectors
class ChatEntryPane extends JTextPane {
private static final char AT = "@".toCharacter()
private static final char BACKSPACE = (char)8
private static final int ENTER = 10
private final UISettings settings
private final List<PersonaOrProfile> members
private JPopupMenu popupMenu
private Point lastPoint
private Component lastComponent
private Action backspaceAction
Closure actionPerformed
ChatEntryPane(UISettings settings, List<PersonaOrProfile> members) {
super()
this.settings = settings
this.members = members
setEditable(true)
addKeyListener(new KeyAdapter() {
@Override
void keyTyped(KeyEvent e) {
if (e.getKeyChar() == AT) {
lastPoint = getCaret().getMagicCaretPosition()
lastComponent = e.getComponent()
if (lastPoint == null || lastComponent == null)
return
SwingUtilities.invokeLater {
showPopupMenu(false)
}
} else if (((int)e.getKeyChar()) == ENTER) {
SwingUtilities.invokeLater {
actionPerformed.call()
}
}
}
})
KeyStroke back = KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0)
InputMap inputMap = getInputMap()
ActionMap actionMap = getActionMap()
Object backObject = inputMap.get(back)
backspaceAction = actionMap.get(backObject)
actionMap.put(backObject, new BackspaceAction(backspaceAction))
}
private String getTextSinceAt(){
final int caretPosition = getCaret().getDot()
int startPosition = caretPosition
while (startPosition > 0) {
if (getText(startPosition - 1, 1) == "@")
break
startPosition --
}
getText(startPosition, caretPosition - startPosition)
}
private void showPopupMenu(boolean filter) {
popupMenu?.setVisible(false)
String typedText = getTextSinceAt()
List<JMenuItem> items = members.stream().
filter({
if (!filter)
return true
justName(it).containsIgnoreCase(typedText)
}).
map({new MemberAction(it)}).
map({new JMenuItem(it)}).
collect(Collectors.toList())
if (items.isEmpty())
return
popupMenu = new JPopupMenu()
items.each {popupMenu.add(it)}
popupMenu.addMenuKeyListener(new MenuKeyListener() {
@Override
void menuKeyTyped(MenuKeyEvent e) {
char keyChar = e.getKeyChar()
if (((int)keyChar) == ENTER)
return
if (keyChar == BACKSPACE) {
backspaceAction.actionPerformed(new ActionEvent(ChatEntryPane.this, 0, null))
SwingUtilities.invokeLater {
showPopupMenu(true)
}
return
}
Document document = getDocument()
int position = getCaret().getDot()
document.insertString(position, "${e.getKeyChar()}", null)
SwingUtilities.invokeLater {
showPopupMenu(true)
}
}
@Override
void menuKeyPressed(MenuKeyEvent e) {
}
@Override
void menuKeyReleased(MenuKeyEvent e) {
}
})
popupMenu.show(lastComponent, (int)lastPoint.getX(), (int)lastPoint.getY())
}
private static String justName(PersonaOrProfile pop) {
String name = pop.getPersona().getHumanReadableName()
name.substring(0, name.indexOf("@"))
}
private class MemberAction extends AbstractAction {
private final PersonaOrProfile personaOrProfile
MemberAction(PersonaOrProfile personaOrProfile) {
this.personaOrProfile = personaOrProfile
putValue(SMALL_ICON, personaOrProfile.getThumbnail())
putValue(NAME, personaOrProfile.getPersona().getHumanReadableName())
}
@Override
void actionPerformed(ActionEvent e) {
final int position = getCaret().getDot()
int startPosition = position
while(startPosition > 0) {
if (getText(startPosition - 1, 1) == "@")
break
startPosition--
}
startPosition = Math.max(startPosition, 0)
setSelectionStart(startPosition)
setSelectionEnd(position)
replaceSelection("")
final String name = personaOrProfile.getPersona().getHumanReadableName()
StyledDocument document = getStyledDocument()
def popLabel = new POPLabel(personaOrProfile, settings)
popLabel.setMaximumSize([200, ProfileConstants.MAX_THUMBNAIL_SIZE] as Dimension)
Style style = document.addStyle("newStyle", null)
StyleConstants.setComponent(style, popLabel)
document.insertString(startPosition, name, style)
popupMenu?.setVisible(false)
popupMenu = null
}
}
private class BackspaceAction extends UIAction {
private final Action delegate
BackspaceAction(Action delegate) {
super("backspace")
this.delegate = delegate
}
@Override
void actionPerformed(ActionEvent e) {
StyledDocument document = getStyledDocument()
int position = getCaret().getDot() - 1
Element element = document.getCharacterElement(position)
if (element.getAttributes().getAttribute(StyleConstants.ComponentAttribute) == null) {
delegate.actionPerformed(e)
return
}
while(position > 0) {
element = document.getCharacterElement(position)
if (element.getAttributes().getAttribute(StyleConstants.ComponentAttribute) == null)
break
delegate.actionPerformed(e)
position--
}
}
}
}

View File

@ -1,32 +0,0 @@
package com.muwire.gui.chat
import javax.swing.JTextField
import javax.swing.text.AttributeSet
import javax.swing.text.BadLocationException
import javax.swing.text.Document
import javax.swing.text.PlainDocument
class ChatTextField extends JTextField {
ChatTextField() {
setEditable(true)
}
protected Document createDefaultModel() {
return new ChatDocument()
}
private static class ChatDocument extends PlainDocument {
@Override
void insertString(int offs, String str, AttributeSet a) throws BadLocationException {
getDocumentProperties().put("filterNewlines", false)
super.insertString(offs, str, a)
}
@Override
String getText(int offset, int length) throws BadLocationException {
String rv = super.getText(offset, length)
return rv
}
}
}