I'm writing a small GUI program. Everything works except that I want to recognize mouse double-clicks. However, I can't recognize mouse clicks (as such) at all, though I can click buttons and select code from a list.
The following code is adapted from Ingo Maier's "The scala.swing package":
import scala.swing._
import scala.swing.event._
object MouseTest extends SimpleGUIApplication {
def top = new MainFrame {
listenTo(this.mouse) // value mouse is not a member of scala.swing.MainFrame
reactions += {
case e: MouseClicked =>
println("Mouse clicked at " + e.point)
}
}
}
I've tried multiple variations: mouse vs. Mouse, SimpleSwingApplication, importing MouseEvent from java.awt.event, etc. The error message is clear enough--no value mouse in MainFrame--so, where is it then? Help!
Maybe that way?
object App extends SimpleSwingApplication {
lazy val ui = new Panel {
listenTo(mouse.clicks)
reactions += {
case e: MouseClicked =>
println("Mouse clicked at " + e.point)
}
}
def top = new MainFrame {
contents = ui
}
}
BTW, SimpleGUIApplication is deprecated
The MouseClicked event has an attribute clicks, which should be 2 if it was a double-click. Have a look at java.awt.event.MouseEvent for the original source if you're curious.
Related
I am currently working on an implementation of the game Othello in Scala and so far it's working pretty nicely. When implementing the GUI though (using Scala Swing) I've stumbled across an issue that I can't seem to fix.
When playing against the computer opponent the Frame seems to be repainted only when the bot is done making its move.
The game is also playable through the terminal and doing so updates the Frame properly every time regardless of the player configuration (Player vs Player or Player vs Computer). Also playing player vs player using the GUI exclusively presents no issues at all.
It might be an oversight on my behalf, but so far I am unable to find a solution and would greatly appreciate any help.
So far I have tried various combinations of revalidating and repainting the individual panels, adding and removing listeners, changing my implementation of the reactor pattern to the one provided by Scala Swing, adding Thread.sleep to see if there could be a scheduling conflict of sorts.
import java.awt.Color
import othello.controller.Controller
import javax.swing.ImageIcon
import javax.swing.border.LineBorder
import scala.swing.event.MouseClicked
import scala.swing.{BorderPanel, BoxPanel, Dimension, FlowPanel, GridPanel, Label, Orientation}
class TablePanel(controller: Controller) extends FlowPanel {
val sides = 32
val sidesColor: Color = Color.lightGray
val squareSize = 52
def tableSize: Int = controller.board.size
def edgeLength: Int = tableSize * squareSize
def rows: BoxPanel = new BoxPanel(Orientation.Vertical) {
background = sidesColor
preferredSize = new Dimension(sides, edgeLength)
contents += new Label {
preferredSize = new Dimension(sides, sides)
}
contents += new GridPanel(tableSize, 1) {
background = sidesColor
for { i <- 1 to rows } contents += new Label(s"$i")
}
}
def columns: GridPanel = new GridPanel(1, tableSize) {
background = sidesColor
preferredSize = new Dimension(edgeLength, sides)
for { i <- 0 until columns } contents += new Label(s"${(i + 65).toChar}")
}
def table: GridPanel = new GridPanel(tableSize, tableSize) {
background = new Color(10, 90, 10)
for {
col <- 0 until columns
row <- 0 until rows
} contents += square(col, row)
}
def square(row: Int, col: Int): Label = new Label {
border = new LineBorder(new Color(30, 30, 30, 140), 1)
preferredSize = new Dimension(squareSize, squareSize)
icon = controller.board.valueOf(col, row) match {
case -1 => new ImageIcon("resources/big_dot.png")
case 0 => new ImageIcon("resources/empty.png")
case 1 => new ImageIcon("resources/black_shadow.png")
case 2 => new ImageIcon("resources/white_shadow.png")
}
listenTo(mouse.clicks)
reactions += {
case _: MouseClicked =>
if (controller.options.contains((col, row))) controller.set(col, row)
else if (controller.board.gameOver) controller.newGame()
else controller.highlight()
}
}
def redraw(): Unit = {
contents.clear
contents += new BorderPanel {
add(rows, BorderPanel.Position.West)
add(new BoxPanel(Orientation.Vertical) {
contents += columns
contents += table
}, BorderPanel.Position.East)
}
repaint
}
}
import scala.swing._
import othello.controller._
import othello.util.Observer
import scala.swing.event.Key
class SwingGui(controller: Controller) extends Frame with Observer {
controller.add(this)
lazy val tablePanel = new TablePanel(controller)
lazy val mainFrame: MainFrame = new MainFrame {
title = "Othello"
menuBar = menus
contents = tablePanel
centerOnScreen
// peer.setAlwaysOnTop(true)
resizable = false
visible = true
}
def menus: MenuBar = new MenuBar {
contents += new Menu("File") {
mnemonic = Key.F
contents += new MenuItem(Action("New Game") {
controller.newGame()
})
contents += new MenuItem(Action("Quit") {
controller.exit()
})
}
contents += new Menu("Edit") {
mnemonic = Key.E
contents += new MenuItem(Action("Undo") {
controller.undo()
})
contents += new MenuItem(Action("Redo") {
controller.redo()
})
}
contents += new Menu("Options") {
mnemonic = Key.O
contents += new MenuItem(Action("Highlight possible moves") {
controller.highlight()
})
contents += new MenuItem(Action("Reduce board size") {
controller.resizeBoard("-")
})
contents += new MenuItem(Action("Increase board size") {
controller.resizeBoard("+")
})
contents += new MenuItem(Action("Reset board size") {
controller.resizeBoard(".")
})
contents += new Menu("Game mode") {
contents += new MenuItem(Action("Player vs. Computer") {
controller.setupPlayers("1")
})
contents += new MenuItem(Action("Player vs. Player") {
controller.setupPlayers("2")
})
}
}
}
def update: Boolean = {
tablePanel.redraw()
mainFrame.pack
mainFrame.centerOnScreen
mainFrame.repaint
true
}
}
The expected behavior is a repainted Frame on every turn. The actual result is the Frame only being repainted after the opponent made a move. It only happens when playing player vs bot exclusively through clicking the UI.
I don't think the problem is in the code you have shown, but I would bet that you are blocking the "event dispatch thread" (the UI thread) with your AI computation for the computer player.
In a Swing app, there is a special thread called the "event dispatch thread" that is responsible for handling messages from the O/S including dealing with repaint messages. All UI event handlers will be invoked on this thread. If you use that thread to do any computations which take a long time (such as a computer move in a game like this), any UI updates will be blocked until the thread becomes free.
This tutorial has more info: https://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html
You need to move the AI onto a background thread and release the event dispatch thread to deal with repaints. This might be tricky to implement if you are not familiar with multi-threaded programs! Good luck.
As Rich suggested, the solution was concurrency. Making the search algorithm a Future-type solved the issue for now. It might not be the cleanest implementation as it is the first time I'm using Future, but this is what it looks like now:
def selectAndSet(): Future[_] = Future(if (!board.gameOver && player.isBot) {
new MoveSelector(this).select() match {
case Success(square) => set(square)
case _ => omitPlayer()
}
selectAndSet()
})(ExecutionContext.global)
I'm having an issue with a project I'm working on. The original code is too big to poste here but I wrote a short version which has the same issue.
Basically, I wrote some kind of chatbox that adds Labels containing the replies to a BoxPanel, one after another. Ideally the user wouldn't need to scroll manually, the scrolling would happen automatically and show the very last reply of the chat.
The issue is that when using scrollBar.peer.setValue(scrollBar.peer.getMaximum),
the chat doesn't automatically scroll all the way down, it never shows the very last line of the chat, even after I revalidate() the chat before scrolling down.
this is what the code looks like:
import scala.swing._
import scala.swing.event._
class testUI extends MainFrame {
title = "testScroll"
preferredSize = new Dimension(400, 400)
val chat = new BoxPanel(Orientation.Vertical)
val inputField = new TextField {
listenTo(mouse.clicks, this.keys)
reactions += {
case KeyPressed(_, Key.Enter, _, _) => if (this.text != "") { printToChat(this.text) }
}
}
val scrollPanel = new ScrollPane(chat)
var scrollBar = scrollPanel.verticalScrollBar
val window = new BorderPanel {
add(inputField, BorderPanel.Position.North)
add(scrollPanel, BorderPanel.Position.Center)
}
contents = window
def printToChat(s: String) {
chat.contents += new Label(s) {
horizontalAlignment = Alignment.Right
border = Swing.EmptyBorder(20, 20, 0, 0) //(top, left, bottom, right)
}
chat.revalidate()
scrollToBottom()
inputField.text = ""
}
def scrollToBottom() {
scrollBar.peer.setValue(scrollBar.peer.getMaximum)
}
}
object testUI {
def main(args: Array[String]) {
val ui = new testUI
ui.visible = true
}
}
Thank you very much in advance :)
EDIT: I solved my issue thanks to camickr by wrapping my previous line of code with Swing.onEDT:
Swing.onEDT(scrollBar.peer.setValue(scrollBar.peer.getMaximum))
instead of just:
scrollBar.peer.setValue(scrollBar.peer.getMaximum)
I have written this Fantom class
using gfx
using fwt
class Test {
Window window := Window {
size = Size( 400, 320 )
SashPane {
Combo {
items = Month.vals
onModify.add( |e| { echo( "items.size is ${e->widget->items->size}" ) } )
},
},
}
Void main() {
window.open
}
}
when I run it, it produces this output:
items.size is 12
items.size is 12
which means that the modify event is being triggered twice. It occurs at the same time the window pops up on the screen, without me having any chance to modify anything on the Combo widget. Why?
This is causing issues in a real class that uses multiple Combo widgets, some of them related and causing a cascade of events that produces unexpected results.
Is there any way this can be prevented, please?
I can confirm that this is an issue.
Looking at the Java source code for the FWT Combo, it's pretty small and doesn't seem to do anything wrong, which leads me to believe that it's a issue with the SWT Combo Widget.
That doesn't help you any, so I had a quick play with the example and found this work around...
...add the onModify event listener after the window has been opened, and the widgets constructed. Do this by using the Window.onOpen() event:
using gfx
using fwt
class Testy {
Void main() {
Window {
size = Size( 400, 320 )
combo := null as Combo
onOpen.add {
combo.onModify.add { echo("Hello Mum!") }
}
SashPane {
combo = Combo { items = Month.vals },
},
}.open
}
}
Now you should only get a Hello Mum! when the combo is actually modified.
I have a Leaflet map with a custom control that consists of a simple form with an <input> and a <button>.
The problem I'm having is that when you submit the form by hitting the Enter key, it also triggers a click event on the map. Interestingly, the originalEvent property of the click event object is a KeyboardEvent with a key code of 13 (the Enter key). Somehow, one is leading to the other and I can't figure out why.
This is what my Control subclass looks like:
import { Control, DomEvent } from 'leaflet';
class ControlParent extends Control {
constructor(el, options) {
super(options);
this.el = el;
}
onAdd() {
const el = this.el;
// keep clicks from hitting the map
DomEvent.disableClickPropagation(el);
return el;
}
}
Can anyone think of what's causing this and what steps I can take to keep it from propagating?
If I run this example code
class ExampleGUIElement extends Panel
{
preferredSize = new Dimension(100, 100)
val rand : Random = Random
override def paintComponent(g: Graphics2D) = g.drawLine(rand.nextInt(10),rand.nextInt(10),rand.nextInt(90) + 10 ,rand.nextInt(90) + 10)
}
object GUITest extends SimpleSwingApplication
{
def top = new MainFrame
{
contents = new ExampleGUIElement
}
}
It obviously only shows the one line that has been draw initially. If I want a periodic repaint I normally would just make a Timer that calls repaint on the most outer JFrame.
But SimpleSwingApplication does not have this method, at least I can not find it, and calling top.repaint instead just does nothing. So how do you periodically repaint the whole thing?
Consider calling peer on the top (see MainFrame), and then calling repaint on that:
top.peer.repaint()
Also, don't forget to call top.pack().
I realize this is dated, but I extracted your Panel into a val and added a timer to repaint it:
def top = new MainFrame
{
val example = new ExampleGUIElement
contents = example
val timer=new javax.swing.Timer(250, Swing.ActionListener(e =>
{
example.repaint()
}))
timer.start()
}