Step 5: Marquee's Messages

Now that the messages are all prepped and ready to go, we can finally draw them! In preparation for this long-awaited moment, add the following fields to Marquee:

private int firstMessage = 0;
private int mousePos = -1;
private String mouseLink;

The firstMessage field holds the index of the first (that is, leftmost) visible message; it is initialized to 0. (For example, after the first message—index 0—has scrolled by, firstMessage will be set to 1.) The mouseLink field holds the URL of the link that's currently under the mouse cursor. We'll use this when we implement the hyperlinks.

Next, make the following changes to the paint() method:

public void paint(Graphics g) {
	// Initialize the drawing buffer
	Image drawBuffer = createImage(size.width, size.height);
	Graphics gDraw = drawBuffer.getGraphics();

	// Clear the background
	gDraw.setColor(backColor);
	gDraw.fillRect(0, 0, size.width, size.height);

	if(errorMsg != null) {
		displayError(gDraw);
		g.drawImage(drawBuffer, 0, 0, null);
		return;
	}

	// Draw the messages
	for(int i = firstMessage; i < numMessages; i++) {
		// Don't draw messages past the right edge
		if(Messages[i].x >= size.width)
			break;

		// Don't draw messages past the left edge
		if(Messages[i].x + Messages[i].width < 0) {
			firstMessage = i + 1;
			continue;
		}

		int y;

		if(Messages[i].link == null) {
			gDraw.setColor(textColor);
			gDraw.setFont(textFont);
			y = getCenteredY(textFontMetrics);
		} else {
			if(mousePos != -1) {
				// If the mouse is on the link...
				if(mousePos >= Messages[i].x && mousePos < Messages[i].x + Messages[i].width) {
					mouseLink = Messages[i].link;
					gDraw.setColor(activeColor);
				} else
					gDraw.setColor(linkColor);
			} else
				gDraw.setColor(linkColor);

			gDraw.setFont(linkFont);
			y = getCenteredY(linkFontMetrics);
		}

		gDraw.drawString(Messages[i].text, Messages[i].x, y);
	}

	g.drawImage(drawBuffer, 0, 0, null);
}

paint in Detail

As usual, we use a for loop to iterate through each message, starting from firstMessage. The first thing we do inside is check to see if the current message, Messages[i], is past the right edge:

for(int i = firstMessage; i < numMessages; i++) {
	// Don't draw messages past the right edge
	if(Messages[i].x >= size.width)
		break;

If the x field is greater than the applet width, we immediately end the loop by using the keyword break. This handy keyword can be used anytime you want to transfer control out of a loop. In this case, we have no more messages to draw, so we resume execution with the statement after the loop:

g.drawImage(drawBuffer, 0, 0, null);

Returning to the loop, we next check if the message has scrolled out of sight to the left:

// Don't draw messages past the left edge
if(Messages[i].x + Messages[i].width < 0) {
	firstMessage = i + 1;
	continue;
}

If so, we simply update our firstMessage field so that we won't draw message i anymore. The next statement, continue, is the counterpart of break. Instead of exiting the loop completely, it merely skips the rest of the code in the loop for this iteration; the loop will still continue. Here, we still have the rest of the messages to draw, so we simply skip the drawing part for this message, which is next....

We declare an integer variable called y that will hold the vertical, centered position of the message.

Then, we see if the current message is a link. If it isn't, we set the appropriate color and font for a plain text message, and calculate y:

if(Messages[i].link == null) {
	gDraw.setColor(textColor);
	gDraw.setFont(textFont);
	y = getCenteredY(textFontMetrics);
}

Otherwise, if the message is a link, we need to check if the mouse cursor is inside the applet and is hovering over the message. We then set the appropriate text color and also mouseLink:

if(mousePos != -1) {
	// If the mouse is on the link...
	if(mousePos >= Messages[i].x && mousePos < Messages[i].x + Messages[i].width) {
		mouseLink = Messages[i].link;
		gDraw.setColor(activeColor);
	} else
		gDraw.setColor(linkColor);
} else
	gDraw.setColor(linkColor);

Whether the mouse is over the message or not, we also set the font to linkFont and calculate y:

gDraw.setFont(linkFont);
y = getCenteredY(linkFontMetrics);

The last thing we do inside the loop is to draw the message itself:

gDraw.drawString(Messages[i].text, Messages[i].x, y);

Scrolling and Looping

If you compile and run the applet now, you won't see anything. Why not? The messages are still past the right edge of the applet! We need to scroll the messages, so add this method at the end of the Marquee class:

// Move messages
private void moveMessages() {
	for(int i = 0; i < numMessages; i++)
		Messages[i].x -= scrollAmount;
}

This simple method iterates through each message and changes their horizontal positions by scrollAmount using the subtraction assignment operator, -=. Also change the run() method to call moveMessages():

public void run() {
	while(true) {
		try {
			repaint();
			thread.sleep(scrollDelay);

			moveMessages();
		}
		catch(InterruptedException e) {
		}
	}
}

Now the messages should parade across the screen—once. To make it loop over and over, we need to reset the message positions after they have all scrolled past. Add these lines to the end of the paint() method:

	g.drawImage(drawBuffer, 0, 0, null);

	// Cycle after all the messages have scrolled past
	if(firstMessage >= numMessages) {
		firstMessage = 0;
		initMessagePositions();
	}
}

We simply see if firstMessage is no longer a valid message index. If it isn't, we set firstMessage back to 0 and call initMessagePositions() to reset the positions.

In the next step, we'll complete Marquee by implementing the messages' hyperlinks.


1