Code Sketch
yoi fgg
Category: Programming
import javax.swing._
import javax.swing.border.EmptyBorder
import java.awt._
import java.awt.event._
import java.io._
import java.nio.file.{Files, Paths}
import java.security.MessageDigest
import java.util.Base64
import scala.collection.mutable
val APP_DIR = new File(System.getProperty("user.home"), ".kojo_private_chat")
val DATA_FILE = new File(APP_DIR, "accounts.dat")
if (!APP_DIR.exists()) {
APP_DIR.mkdirs()
}
case class User(
name: String,
passwordHash: String,
friends: mutable.Set[String],
incoming: mutable.Set[String],
outgoing: mutable.Set[String]
) extends Serializable
case class ChatMessage(
from: String,
to: String,
text: String,
time: Long
) extends Serializable
class Database extends Serializable {
val users: mutable.Map[String, User] = mutable.Map.empty
val messages: mutable.ArrayBuffer[ChatMessage] = mutable.ArrayBuffer.empty
}
object ChatStore {
private def hash(value: String): String = {
val md = MessageDigest.getInstance("SHA-256")
val bytes = md.digest(value.getBytes("UTF-8"))
bytes.map("%02x".format(_)).mkString
}
def passwordHash(password: String): String = hash(password)
def cleanName(s: String): String = {
if (s == null) "" else s.trim.replaceAll("\\s+", " ")
}
def normalizeName(s: String): String = cleanName(s).toLowerCase
def validPassword(p: String): Boolean = {
p != null && p.length >= 4
}
def load(): Database = {
if (!DATA_FILE.exists()) {
return new Database
}
try {
val in = new ObjectInputStream(new FileInputStream(DATA_FILE))
val obj = in.readObject()
in.close()
obj match {
case d: Database => d
case _ => new Database
}
} catch {
case _: Throwable => new Database
}
}
def save(db: Database): Unit = {
try {
if (!APP_DIR.exists()) {
APP_DIR.mkdirs()
}
val temp = new File(APP_DIR, "accounts.tmp")
val out = new ObjectOutputStream(new FileOutputStream(temp))
out.writeObject(db)
out.flush()
out.close()
if (DATA_FILE.exists()) {
DATA_FILE.delete()
}
temp.renameTo(DATA_FILE)
} catch {
case _: Throwable =>
}
}
}
val db = ChatStore.load()
def findUser(name: String): Option[User] = {
val key = ChatStore.normalizeName(name)
db.users.values.find(u => ChatStore.normalizeName(u.name) == key)
}
def uniqueUsername(name: String): Boolean = {
findUser(name).isEmpty
}
def safeUser(name: String): Option[User] = findUser(name)
def displayName(name: String): String = {
safeUser(name).map(_.name).getOrElse(name)
}
class RoundedPanel(bg: Color, radius: Int) extends JPanel {
setOpaque(false)
override def paintComponent(g: Graphics): Unit = {
val g2 = g.create().asInstanceOf[Graphics2D]
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
g2.setColor(bg)
g2.fillRoundRect(0, 0, getWidth, getHeight, radius, radius)
g2.dispose()
super.paintComponent(g)
}
}
class ChatPanel(currentUser: User, otherUser: User, onBack: () => Unit) extends JPanel {
private val messagesArea = new JTextArea()
private val input = new JTextField()
private val sendButton = new JButton("SEND")
private val backButton = new JButton("BACK")
private val refreshButton = new JButton("REFRESH")
setLayout(new BorderLayout(10, 10))
setBackground(new Color(245, 247, 250))
setBorder(new EmptyBorder(12, 12, 12, 12))
val header = new JPanel(new BorderLayout())
header.setBackground(new Color(24, 119, 242))
header.setBorder(new EmptyBorder(10, 12, 10, 12))
val title = new JLabel(" " + otherUser.name)
title.setForeground(Color.WHITE)
title.setFont(new Font("SansSerif", Font.BOLD, 20))
backButton.setFocusPainted(false)
refreshButton.setFocusPainted(false)
header.add(backButton, BorderLayout.WEST)
header.add(title, BorderLayout.CENTER)
header.add(refreshButton, BorderLayout.EAST)
add(header, BorderLayout.NORTH)
messagesArea.setEditable(false)
messagesArea.setLineWrap(true)
messagesArea.setWrapStyleWord(true)
messagesArea.setFont(new Font("SansSerif", Font.PLAIN, 15))
messagesArea.setBackground(Color.WHITE)
messagesArea.setBorder(new EmptyBorder(12, 12, 12, 12))
val scroll = new JScrollPane(messagesArea)
scroll.setBorder(BorderFactory.createLineBorder(new Color(220, 224, 230)))
add(scroll, BorderLayout.CENTER)
val bottom = new JPanel(new BorderLayout(8, 8))
bottom.setBackground(new Color(245, 247, 250))
input.setFont(new Font("SansSerif", Font.PLAIN, 15))
input.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(new Color(200, 205, 212)),
new EmptyBorder(8, 8, 8, 8)
))
sendButton.setBackground(new Color(24, 119, 242))
sendButton.setForeground(Color.WHITE)
sendButton.setFocusPainted(false)
bottom.add(input, BorderLayout.CENTER)
bottom.add(sendButton, BorderLayout.EAST)
add(bottom, BorderLayout.SOUTH)
def refreshMessages(): Unit = {
messagesArea.setText("")
val relevant = db.messages.filter { m =>
(ChatStore.normalizeName(m.from) == ChatStore.normalizeName(currentUser.name) &&
ChatStore.normalizeName(m.to) == ChatStore.normalizeName(otherUser.name)) ||
(ChatStore.normalizeName(m.from) == ChatStore.normalizeName(otherUser.name) &&
ChatStore.normalizeName(m.to) == ChatStore.normalizeName(currentUser.name))
}
relevant.foreach { m =>
val date = new java.util.Date(m.time)
messagesArea.append(
m.from + " [" + date.toString + "]\n" +
m.text + "\n\n"
)
}
messagesArea.setCaretPosition(messagesArea.getDocument.getLength)
}
def sendMessage(): Unit = {
val text = input.getText.trim
if (text.nonEmpty) {
val stillFriends =
currentUser.friends.exists(x =>
ChatStore.normalizeName(x) == ChatStore.normalizeName(otherUser.name)
)
if (stillFriends) {
db.messages += ChatMessage(
currentUser.name,
otherUser.name,
text,
System.currentTimeMillis()
)
ChatStore.save(db)
input.setText("")
refreshMessages()
} else {
JOptionPane.showMessageDialog(
this,
"You are no longer connected with this person.",
"Chat unavailable",
JOptionPane.WARNING_MESSAGE
)
onBack()
}
}
}
sendButton.addActionListener(new ActionListener {
override def actionPerformed(e: ActionEvent): Unit = sendMessage()
})
input.addActionListener(new ActionListener {
override def actionPerformed(e: ActionEvent): Unit = sendMessage()
})
refreshButton.addActionListener(new ActionListener {
override def actionPerformed(e: ActionEvent): Unit = refreshMessages()
})
backButton.addActionListener(new ActionListener {
override def actionPerformed(e: ActionEvent): Unit = onBack()
})
refreshMessages()
}
class MainPanel(currentUser: User, frame: JFrame) extends JPanel {
private var selectedFriend: Option[User] = None
private val title = new JLabel("Private Chat")
private val userLabel = new JLabel()
private val searchField = new JTextField()
private val searchButton = new JButton("SEARCH PEOPLE")
private val resultsPanel = new JPanel()
private val friendsPanel = new JPanel()
private val incomingPanel = new JPanel()
setLayout(new BorderLayout())
setBackground(new Color(245, 247, 250))
val top = new JPanel(new BorderLayout())
top.setBackground(new Color(24, 119, 242))
top.setBorder(new EmptyBorder(12, 16, 12, 16))
title.setForeground(Color.WHITE)
title.setFont(new Font("SansSerif", Font.BOLD, 24))
userLabel.setForeground(Color.WHITE)
userLabel.setFont(new Font("SansSerif", Font.PLAIN, 14))
userLabel.setHorizontalAlignment(SwingConstants.RIGHT)
top.add(title, BorderLayout.WEST)
top.add(userLabel, BorderLayout.EAST)
add(top, BorderLayout.NORTH)
val center = new JPanel()
center.setLayout(new BoxLayout(center, BoxLayout.Y_AXIS))
center.setBackground(new Color(245, 247, 250))
center.setBorder(new EmptyBorder(14, 14, 14, 14))
val searchCard = new JPanel(new BorderLayout(8, 8))
searchCard.setBackground(Color.WHITE)
searchCard.setBorder(new EmptyBorder(12, 12, 12, 12))
searchField.setFont(new Font("SansSerif", Font.PLAIN, 15))
searchField.setBorder(BorderFactory.createLineBorder(new Color(210, 214, 220)))
searchButton.setBackground(new Color(24, 119, 242))
searchButton.setForeground(Color.WHITE)
searchButton.setFocusPainted(false)
searchCard.add(searchField, BorderLayout.CENTER)
searchCard.add(searchButton, BorderLayout.EAST)
center.add(searchCard)
center.add(Box.createVerticalStrut(10))
val resultTitle = new JLabel("SEARCH RESULTS")
resultTitle.setFont(new Font("SansSerif", Font.BOLD, 13))
resultTitle.setForeground(new Color(90, 95, 105))
center.add(resultTitle)
center.add(Box.createVerticalStrut(5))
resultsPanel.setLayout(new BoxLayout(resultsPanel, BoxLayout.Y_AXIS))
resultsPanel.setBackground(new Color(245, 247, 250))
val resultScroll = new JScrollPane(resultsPanel)
resultScroll.setPreferredSize(new Dimension(600, 130))
resultScroll.setBorder(BorderFactory.createLineBorder(new Color(225, 228, 233)))
center.add(resultScroll)
center.add(Box.createVerticalStrut(12))
val incomingTitle = new JLabel("FRIEND REQUESTS")
incomingTitle.setFont(new Font("SansSerif", Font.BOLD, 13))
incomingTitle.setForeground(new Color(90, 95, 105))
center.add(incomingTitle)
center.add(Box.createVerticalStrut(5))
incomingPanel.setLayout(new BoxLayout(incomingPanel, BoxLayout.Y_AXIS))
incomingPanel.setBackground(new Color(245, 247, 250))
val incomingScroll = new JScrollPane(incomingPanel)
incomingScroll.setPreferredSize(new Dimension(600, 130))
incomingScroll.setBorder(BorderFactory.createLineBorder(new Color(225, 228, 233)))
center.add(incomingScroll)
center.add(Box.createVerticalStrut(12))
val friendsTitle = new JLabel("YOUR FRIENDS")
friendsTitle.setFont(new Font("SansSerif", Font.BOLD, 13))
friendsTitle.setForeground(new Color(90, 95, 105))
center.add(friendsTitle)
center.add(Box.createVerticalStrut(5))
friendsPanel.setLayout(new BoxLayout(friendsPanel, BoxLayout.Y_AXIS))
friendsPanel.setBackground(new Color(245, 247, 250))
val friendsScroll = new JScrollPane(friendsPanel)
friendsScroll.setPreferredSize(new Dimension(600, 170))
friendsScroll.setBorder(BorderFactory.createLineBorder(new Color(225, 228, 233)))
center.add(friendsScroll)
add(center, BorderLayout.CENTER)
val bottom = new JPanel(new FlowLayout(FlowLayout.RIGHT))
bottom.setBackground(new Color(245, 247, 250))
val profileButton = new JButton("PROFILE / PASSWORD")
val logoutButton = new JButton("LOG OUT")
profileButton.setFocusPainted(false)
logoutButton.setFocusPainted(false)
bottom.add(profileButton)
bottom.add(logoutButton)
add(bottom, BorderLayout.SOUTH)
def makeUserRow(user: User): JPanel = {
val row = new JPanel(new BorderLayout(8, 8))
row.setBackground(Color.WHITE)
row.setBorder(new EmptyBorder(8, 10, 8, 10))
row.setMaximumSize(new Dimension(1000, 52))
val name = new JLabel(user.name)
name.setFont(new Font("SansSerif", Font.BOLD, 15))
row.add(name, BorderLayout.CENTER)
row
}
def makeSearchRow(user: User): JPanel = {
val row = new JPanel(new BorderLayout(8, 8))
row.setBackground(Color.WHITE)
row.setBorder(new EmptyBorder(8, 10, 8, 10))
row.setMaximumSize(new Dimension(1000, 52))
val name = new JLabel(user.name)
name.setFont(new Font("SansSerif", Font.BOLD, 15))
val action = new JButton()
val isFriend = currentUser.friends.exists(
x => ChatStore.normalizeName(x) == ChatStore.normalizeName(user.name)
)
val alreadySent = currentUser.outgoing.exists(
x => ChatStore.normalizeName(x) == ChatStore.normalizeName(user.name)
)
val incoming = currentUser.incoming.exists(
x => ChatStore.normalizeName(x) == ChatStore.normalizeName(user.name)
)
if (isFriend) {
action.setText("CHAT")
} else if (incoming) {
action.setText("REQUEST RECEIVED")
action.setEnabled(false)
} else if (alreadySent) {
action.setText("REQUEST SENT")
action.setEnabled(false)
} else {
action.setText("ADD FRIEND")
}
action.setFocusPainted(false)
action.addActionListener(new ActionListener {
override def actionPerformed(e: ActionEvent): Unit = {
if (isFriend) {
openChat(user)
} else {
sendRequest(user)
}
}
})
row.add(name, BorderLayout.CENTER)
row.add(action, BorderLayout.EAST)
row
}
def sendRequest(target: User): Unit = {
val exists =
currentUser.outgoing.exists(
x => ChatStore.normalizeName(x) == ChatStore.normalizeName(target.name)
)
if (!exists) {
currentUser.outgoing += target.name
target.incoming += currentUser.name
ChatStore.save(db)
JOptionPane.showMessageDialog(
frame,
"Friend request sent to " + target.name,
"Friend request",
JOptionPane.INFORMATION_MESSAGE
)
refreshAll()
}
}
def acceptRequest(from: User): Unit = {
currentUser.incoming -= from.name
from.outgoing -= currentUser.name
currentUser.friends += from.name
from.friends += currentUser.name
ChatStore.save(db)
JOptionPane.showMessageDialog(
frame,
"You and " + from.name + " are now friends.",
"Friend added",
JOptionPane.INFORMATION_MESSAGE
)
refreshAll()
}
def rejectRequest(from: User): Unit = {
currentUser.incoming -= from.name
from.outgoing -= currentUser.name
ChatStore.save(db)
refreshAll()
}
def openChat(friend: User): Unit = {
selectedFriend = Some(friend)
val chat = new ChatPanel(
currentUser,
friend,
() => {
frame.setContentPane(this)
frame.revalidate()
frame.repaint()
refreshAll()
}
)
frame.setContentPane(chat)
frame.revalidate()
frame.repaint()
chat.requestFocusInWindow()
}
def searchPeople(): Unit = {
resultsPanel.removeAll()
val query = ChatStore.normalizeName(searchField.getText)
if (query.isEmpty) {
resultsPanel.add(new JLabel("Type a name to search."))
} else {
val found = db.users.values
.filter(u => ChatStore.normalizeName(u.name).contains(query))
.filter(u => ChatStore.normalizeName(u.name) != ChatStore.normalizeName(currentUser.name))
.toSeq
.sortBy(_.name.toLowerCase)
if (found.isEmpty) {
resultsPanel.add(new JLabel("No user found."))
} else {
found.foreach { u =>
resultsPanel.add(makeSearchRow(u))
resultsPanel.add(Box.createVerticalStrut(4))
}
}
}
resultsPanel.revalidate()
resultsPanel.repaint()
}
def refreshIncoming(): Unit = {
incomingPanel.removeAll()
val names = currentUser.incoming.toSeq.sorted
if (names.isEmpty) {
incomingPanel.add(new JLabel("No pending friend requests."))
} else {
names.foreach { name =>
safeUser(name) match {
case Some(user) =>
val row = new JPanel(new BorderLayout(8, 8))
row.setBackground(Color.WHITE)
row.setBorder(new EmptyBorder(8, 10, 8, 10))
row.setMaximumSize(new Dimension(1000, 55))
val label = new JLabel(user.name)
label.setFont(new Font("SansSerif", Font.BOLD, 15))
val buttons = new JPanel(new FlowLayout(FlowLayout.RIGHT, 4, 0))
buttons.setOpaque(false)
val accept = new JButton("ACCEPT")
val reject = new JButton("DECLINE")
accept.setFocusPainted(false)
reject.setFocusPainted(false)
accept.addActionListener(new ActionListener {
override def actionPerformed(e: ActionEvent): Unit = {
acceptRequest(user)
}
})
reject.addActionListener(new ActionListener {
override def actionPerformed(e: ActionEvent): Unit = {
rejectRequest(user)
}
})
buttons.add(accept)
buttons.add(reject)
row.add(label, BorderLayout.CENTER)
row.add(buttons, BorderLayout.EAST)
incomingPanel.add(row)
incomingPanel.add(Box.createVerticalStrut(4))
case None =>
currentUser.incoming -= name
}
}
}
incomingPanel.revalidate()
incomingPanel.repaint()
}
def refreshFriends(): Unit = {
friendsPanel.removeAll()
val names = currentUser.friends.toSeq.sorted
if (names.isEmpty) {
friendsPanel.add(new JLabel("No friends yet. Search people above and send a request."))
} else {
names.foreach { name =>
safeUser(name) match {
case Some(user) =>
val row = new JPanel(new BorderLayout(8, 8))
row.setBackground(Color.WHITE)
row.setBorder(new EmptyBorder(8, 10, 8, 10))
row.setMaximumSize(new Dimension(1000, 55))
val label = new JLabel(user.name)
label.setFont(new Font("SansSerif", Font.BOLD, 15))
val chat = new JButton("OPEN PRIVATE CHAT")
chat.setBackground(new Color(24, 119, 242))
chat.setForeground(Color.WHITE)
chat.setFocusPainted(false)
chat.addActionListener(new ActionListener {
override def actionPerformed(e: ActionEvent): Unit = {
openChat(user)
}
})
row.add(label, BorderLayout.CENTER)
row.add(chat, BorderLayout.EAST)
friendsPanel.add(row)
friendsPanel.add(Box.createVerticalStrut(4))
case None =>
currentUser.friends -= name
}
}
}
friendsPanel.revalidate()
friendsPanel.repaint()
}
def refreshAll(): Unit = {
userLabel.setText("Signed in: " + currentUser.name)
refreshIncoming()
refreshFriends()
searchPeople()
}
profileButton.addActionListener(new ActionListener {
override def actionPerformed(e: ActionEvent): Unit = {
val newPassword = JOptionPane.showInputDialog(
frame,
"Enter new password:",
"Change Password",
JOptionPane.PLAIN_MESSAGE
)
if (newPassword != null) {
if (ChatStore.validPassword(newPassword)) {
currentUser.copy(passwordHash = ChatStore.passwordHash(newPassword))
val replacement = currentUser.copy(
passwordHash = ChatStore.passwordHash(newPassword),
friends = currentUser.friends,
incoming = currentUser.incoming,
outgoing = currentUser.outgoing
)
db.users.update(ChatStore.normalizeName(currentUser.name), replacement)
ChatStore.save(db)
JOptionPane.showMessageDialog(
frame,
"Password changed successfully.",
"Profile",
JOptionPane.INFORMATION_MESSAGE
)
} else {
JOptionPane.showMessageDialog(
frame,
"Password must contain at least 4 characters.",
"Invalid password",
JOptionPane.WARNING_MESSAGE
)
}
}
}
})
logoutButton.addActionListener(new ActionListener {
override def actionPerformed(e: ActionEvent): Unit = {
showLogin(frame)
}
})
searchButton.addActionListener(new ActionListener {
override def actionPerformed(e: ActionEvent): Unit = {
searchPeople()
}
})
searchField.addActionListener(new ActionListener {
override def actionPerformed(e: ActionEvent): Unit = {
searchPeople()
}
})
refreshAll()
}
def showLogin(frame: JFrame): Unit = {
val panel = new JPanel(new GridBagLayout())
panel.setBackground(new Color(245, 247, 250))
val card = new JPanel(new GridBagLayout())
card.setBackground(Color.WHITE)
card.setBorder(new EmptyBorder(28, 34, 28, 34))
val c = new GridBagConstraints()
c.insets = new Insets(7, 7, 7, 7)
c.fill = GridBagConstraints.HORIZONTAL
val logo = new JLabel("PRIVATE CHAT")
logo.setFont(new Font("SansSerif", Font.BOLD, 28))
logo.setForeground(new Color(24, 119, 242))
val subtitle = new JLabel("Private friend-to-friend messaging")
subtitle.setForeground(new Color(90, 95, 105))
val nameField = new JTextField()
val passwordField = new JPasswordField()
nameField.setPreferredSize(new Dimension(300, 38))
passwordField.setPreferredSize(new Dimension(300, 38))
val loginButton = new JButton("SIGN IN")
val registerButton = new JButton("CREATE ACCOUNT")
loginButton.setBackground(new Color(24, 119, 242))
loginButton.setForeground(Color.WHITE)
loginButton.setFocusPainted(false)
registerButton.setFocusPainted(false)
c.gridx = 0
c.gridy = 0
card.add(logo, c)
c.gridy = 1
card.add(subtitle, c)
c.gridy = 2
card.add(new JLabel("Name"), c)
c.gridy = 3
card.add(nameField, c)
c.gridy = 4
card.add(new JLabel("Password"), c)
c.gridy = 5
card.add(passwordField, c)
c.gridy = 6
card.add(loginButton, c)
c.gridy = 7
card.add(registerButton, c)
panel.add(card)
frame.setContentPane(panel)
frame.revalidate()
frame.repaint()
def signIn(): Unit = {
val name = ChatStore.cleanName(nameField.getText)
val password = new String(passwordField.getPassword)
if (name.isEmpty || password.isEmpty) {
JOptionPane.showMessageDialog(
frame,
"Please enter name and password.",
"Sign in",
JOptionPane.WARNING_MESSAGE
)
return
}
safeUser(name) match {
case Some(user) =>
if (user.passwordHash == ChatStore.passwordHash(password)) {
val main = new MainPanel(user, frame)
frame.setContentPane(main)
frame.revalidate()
frame.repaint()
} else {
JOptionPane.showMessageDialog(
frame,
"Incorrect password.",
"Sign in failed",
JOptionPane.ERROR_MESSAGE
)
}
case None =>
JOptionPane.showMessageDialog(
frame,
"Account not found. Create an account first.",
"Sign in failed",
JOptionPane.ERROR_MESSAGE
)
}
}
def createAccount(): Unit = {
val name = ChatStore.cleanName(nameField.getText)
val password = new String(passwordField.getPassword)
if (name.length < 2) {
JOptionPane.showMessageDialog(
frame,
"Name must contain at least 2 characters.",
"Create account",
JOptionPane.WARNING_MESSAGE
)
return
}
if (!ChatStore.validPassword(password)) {
JOptionPane.showMessageDialog(
frame,
"Password must contain at least 4 characters.",
"Create account",
JOptionPane.WARNING_MESSAGE
)
return
}
if (!uniqueUsername(name)) {
JOptionPane.showMessageDialog(
frame,
"That name is already registered. Please choose another name.",
"Create account",
JOptionPane.WARNING_MESSAGE
)
return
}
val user = User(
name,
ChatStore.passwordHash(password),
mutable.Set.empty[String],
mutable.Set.empty[String],
mutable.Set.empty[String]
)
db.users += (ChatStore.normalizeName(name) -> user)
ChatStore.save(db)
JOptionPane.showMessageDialog(
frame,
"Account created successfully. You can now sign in.",
"Account created",
JOptionPane.INFORMATION_MESSAGE
)
nameField.setText(name)
passwordField.setText("")
passwordField.requestFocusInWindow()
}
loginButton.addActionListener(new ActionListener {
override def actionPerformed(e: ActionEvent): Unit = signIn()
})
registerButton.addActionListener(new ActionListener {
override def actionPerformed(e: ActionEvent): Unit = createAccount()
})
passwordField.addActionListener(new ActionListener {
override def actionPerformed(e: ActionEvent): Unit = signIn()
})
SwingUtilities.invokeLater(new Runnable {
override def run(): Unit = {
nameField.requestFocusInWindow()
}
})
}
val frame = new JFrame("Private Chat")
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE)
frame.setMinimumSize(new Dimension(760, 620))
frame.setContentPane(new JPanel())
frame.setSize(820, 680)
frame.setLocationRelativeTo(null)
showLogin(frame)
frame.setVisible(true)
SwingUtilities.invokeLater(new Runnable {
override def run(): Unit = {
frame.requestFocus()
}
})