Reputation: 850
The following code was taken from this post: How to create Scala swing wrapper classes with SuperMixin?
import scala.swing._
import javax.swing.JPopupMenu
class PopupMenu extends Component with SequentialContainer.Wrapper {
override lazy val peer: JPopupMenu = new JPopupMenu with SuperMixin
def show(invoker: Component, x: Int, y: Int): Unit = peer.show(invoker.peer, x, y)
}
I've been trying to make custom wrappers so need to understand this, which is simple enough but since I'm only starting to get acquainted with Scala so I'm a little unsure about traits. So what I've been hearing is that traits is like multiple inheritance and you can mix and match them?
I've drawn a diagram representing where PopupMenu sits within the whole inheritance structure. Just to clarify a few things:
1) It seems to override lazy val peer:JComponent from Component and also gets the contents property from SequentialContainer.Wrapper? (purple text) Is that right?
2) Sequential.Wrapper also has a abstract def peer: JComponent.. but this isn't the one being overriden, so it isn't used at all here?
3) What's confusing is that Component and Sequential.Wrapper have some identical properties: both of them have def publish and def subscribe (red text).. but the one that the popupMenu will use is subscribe/publish from the Component class?
4) why can't we write PopupMenu extends SequentialContainer.Wrapper with Component instead?
Hopefully that isn't too many questions at once. Help would be much appreciated, I'm a beginner to Scala..
Upvotes: 4
Views: 484
Reputation: 67290
I'll answer using the numbers of your questions:
Correct
Correct. The top trait is UIElement
which defines an abstract member def peer: java.awt.Component
. Then you have Container
which merely adds abstract member def contents: Seq[Component]
to be able to read the child components. Container.Wrapper
is a concrete implementation of Container
which assumes (abstractly) the Java peer is a javax.swing.JComponent
. Note that in Java's own hierarchy, javax.swing.JComponent
is a sub-type of java.awt.Component
, so there is no conflict. Sub-types can refine the types of their members ("covariance"). SequentialContainer
refines Container
by saying that contents
is a mutable buffer (instead of the read-only sequence). Consequently, its implementation SequentialContainer.Wrapper
mixes in Container.Wrapper
but replaces the contents
by a standard Scala buffer. At no point has a concrete peer
been given, yet. For convenience, Component
does implement that member, but then as you have seen, the final class PopupMenu
overrides the peer
. Because of the way the type system works, all the participating traits can access peer
, but only PopupMenu
"knows" that the type has been refined to be javax.swing.JPopupMenu
. For example SequentialContainer.Wrapper
only knows there is a javax.swing.JComponent
, and so it can use this part of the API of the peer.
The Publisher
trait is introduced by UIElement
, so you will find it in all types deriving from UIElement
. There is nothing wrong with having the same trait appear multiple times in the hierarchy. In the final class, there is only one instance of Publisher
, there do not exist multiple "versions" of it. Even if Publisher
had not been defined at the root, but independently in for example Component
and SequentialContainer.Wrapper
, you would only get one instance in the final class.
This is an easy one. In Scala you can only extend one class, but mix in any number of traits. Component
is a class while all other things else are traits. It's class A extends <trait-or-class> with <trait> with <trait> ...
.
To sum up, all GUI elements inherit from trait UIElement
which is backed up by a java.awt.Component
. Elements which have child elements use trait Container
, and all the normal panel type elements which allow you to add and remove elements in a specific order use SequentialContainer
. (Not all panels have a sequential order, for example BorderPanel
does not). These are abstract interfaces, to get all the necessary implementations, you have the .Wrapper
types. Finally to get a useable class, you have Component
which extends UIElement
and requires that the peer is javax.swing.JComponent
, so it can implement all the standard functionality.
When you implement a new wrapper, you usually use Component
and refine the peer
type so that you can access the specific functionality of that peer (e.g. the show
method of JPopupMenu
).
Upvotes: 2