Tải bản đầy đủ
3 More cryptic: Enigma machine, version 2

3 More cryptic: Enigma machine, version 2

Tải bản đầy đủ

248

CHAPTER 9

From app to applet

Figure 9.8 Quite an improvement:
the Enigma emulator acquires a
printout display and rotors, as well
as an attractive shaded backdrop.

9.3.1

The Rotor class, version 2: giving the cipher a visual presence
The Rotor class needs a makeover to turn it into a fully fledged custom node, allowing
users to interact with it. Because this code is being integrated into the existing class,
I’ve taken the liberty of snipping (omitting for the sake of brevity) the bulk of the
code from the previous version, as you’ll see from listing 9.8. (It may not save many
trees, but it could help save a few branches.) The snipped parts have been marked
with bold comments to show what has been dropped and where. Refer to listing 9.1
for a reminder of what’s missing.
Listing 9.8 Rotor.fx (version 2—changes only)
package jfxia.chapter9;
import
import
import
import
import
import
import
import
import
import

javafx.scene.Node;
javafx.scene.CustomNode;
javafx.scene.Group;
javafx.scene.input.MouseEvent;
javafx.scene.paint.Color;
javafx.scene.paint.LinearGradient;
javafx.scene.paint.Stop;
javafx.scene.shape.Polygon;
javafx.scene.shape.Rectangle;
javafx.scene.text.Font;

Licensed to JEROME RAYMOND

249

More cryptic: Enigma machine, version 2
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
import javafx.scene.text.TextOrigin;
package class Rotor extends CustomNode {
// [Snipped variables: see previous version]
def
def
def
def
def

fontSize:Integer = 40;
width:Number = 60;
height:Number = 60;
buttonHeight:Number = 20.0;
letterFont:Font = Font.font(
"Helvetic" , FontWeight.BOLD, fontSize

Sizing and font
constants

);
// [Snipped init block: see previous version]
override function create() : Node {
var r:Node;
var t:Node;
Group {
Up arrow
content: [
polygon
Polygon {
points: [
width/2 , 0.0 ,
0.0 , buttonHeight ,
width , buttonHeight
];
fill: Color.BEIGE;
onMouseClicked: function(ev:MouseEvent) {
rotorPosition = if(rotorPosition==0) 25
else rotorPosition-1;
}
} ,
r = Rectangle {
Rotor
layoutY: buttonHeight;
display rim
width: width;
height: height;
fill: LinearGradient {
proportional: false;
endX:0; endY:height;
stops: [
Stop { offset: 0.0;
color: Color.DARKSLATEGRAY; } ,
Stop { offset: 0.25;
color: Color.DARKGRAY; } ,
Stop { offset: 1.0;
color: Color.BLACK; }
]
}
} ,
Rectangle {
Rotor
layoutX: 4;
display body
layoutY: buttonHeight;
width: width-8;
height: height;
fill: LinearGradient {

Licensed to JEROME RAYMOND

Move
rotor
back
one

250

CHAPTER 9

From app to applet

proportional: false;
endX:0; endY:height;
stops: [
Stop { offset: 0.0;
color: Color.DARKGRAY; } ,
Stop { offset: 0.25;
color: Color.WHITE; } ,
Stop { offset: 0.75;
color: Color.DARKGRAY; } ,
Stop { offset: 1.0;
color: Color.BLACK; }
]
}
Rotor current
} ,
letter
t = Text {
layoutX: bind Util.center(r,t,true);
layoutY: bind buttonHeight +
Util.center(r,t,false);
content: bind posToChr(rotorPosition);
font: letterFont;
textOrigin: TextOrigin.TOP;
} ,
Polygon {
Down arrow
layoutY: buttonHeight+height;
polygon
points: [
0.0 , 0.0 ,
width/2 , buttonHeight ,
width , 0.0
Move rotor
];
forward one
fill: Color.BEIGE;
onMouseClicked: function(ev:MouseEvent) {
rotorPosition = if(rotorPosition==25) 0
else rotorPosition+1;
}
}
];
}
}
// [Snipped encode(), nextPosition(): see previous version]
}
// [Snipped posToChr(), chrToPos(): see previous version]

The new Rotor class contains a hefty set of additions. The scene graph, as assembled
by create(), brings together several layered elements to form the final image. The
graph is structured around a Group, at the top and tail of which are Polygon shapes.
As its name suggests, the Polygon is a node that allows its silhouette to be custom
defined from a sequence of coordinate pairs. The values in the points sequence are
alternate x and y positions. They form the outline of a shape, with the last coordinate
connecting back to the first to seal the perimeter. Both of our polygons are simple triangles, with points at the extreme left, extreme right, and in the middle, forming up
and down arrows.

Licensed to JEROME RAYMOND

251

More cryptic: Enigma machine, version 2

The rest of the scene graph consists of familiar components: two gradient-filled
rectangles and a text node, forming the rotor body between the arrows.
The arrow polygons have mouse handlers attached to them to change the current
rotorPosition. As rotor positions form a circle, condition logic wraps rotorPosition
around when it overshoots the start or end of its range. The turnover position is
ignored, you’ll note, because we don’t want rotors clocking each other as we’re manually adjusting them.
So that’s the finished Rotor. Now for the Paper class.

9.3.2

The Paper class: making a permanent output record
The Paper class is a neat little display node, with five lines of text that are scaled vertically to create the effect of the lines vanishing over a curved surface. We’re employing
a fixed-width font, for that authentic manual typewriter look. Check out figure 9.9.
You can almost hear the clack-clack-clack of those levers, hammering out each letter
as the keys are pressed.

Figure 9.9 Each line of our Paper
node is scaled to create the optical
effect of a surface curving away, to
accompany the shading of the
background Rectangle.

The real Enigma didn’t have a paper output. The machines were designed to be carried at the frontline of a battle, and their rotor mechanics and battery were bulky
enough without adding a stationery cupboard full of paper and ink ribbons. But we’ve
moved on a little in the 80-plus years since the Enigma was first developed, and while a
1200 dpi laser printer might be stretching credibility a little too far, we can at least give
our emulator a period teletype display. The code is in listing 9.9.
Listing 9.9 Paper.fx
package jfxia.chapter9;
import
import
import
import
import
import
import
import
import
import
import

javafx.scene.CustomNode;
javafx.scene.Group;
javafx.scene.Node;
javafx.scene.effect.DropShadow;
javafx.scene.input.MouseEvent;
javafx.scene.layout.VBox;
javafx.scene.paint.Color;
javafx.scene.paint.LinearGradient;
javafx.scene.paint.Stop;
javafx.scene.shape.Rectangle;
javafx.scene.text.Font;

Licensed to JEROME RAYMOND

252

CHAPTER 9

From app to applet

import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
import javafx.scene.text.TextOrigin;
package class Paper extends CustomNode {
package var width:Number = 300.0;
public-read var height:Number;
def lines:Integer= 5;
def font:Font = Font.font(
"Courier" , FontWeight.REGULAR , 20
);
def paper:Text[] = for(i in [0..def y:Number = height;
def scale:Number = 1.0 - ((lines-1-i)*0.15);
def lineHeight:Number = 25*scale;
height = height+lineHeight;
Text {
layoutY: y;
font: font;
textOrigin: TextOrigin.TOP;
scaleY: scale;
clip: Rectangle {
width: width; height: lineHeight;
}
};
}

Sequence of
text lines
Scale and position
to create curve

override function create() : Node {
Group {
Shaded paper
content: [
backdrop
Rectangle {
width: width;
height: height;
fill: LinearGradient {
proportional: true;
endX: 0; endY: 1;
stops: [
Stop { offset:0.0;
color: Color.SLATEGRAY; } ,
Stop { offset:0.2;
color: Color.WHITE; } ,
Stop { offset:1.0;
color: Color.LIGHTGRAY; }
];
}
effect: DropShadow {
offsetX: 0; offsetY: 10;
color: Color.CHOCOLATE;
};
Text nodes
} ,
expanded in place
Group { content: paper; }
];
onMouseClicked: function(ev:MouseEvent) {
Scroll up
add("\n");
when clicked
}

Licensed to JEROME RAYMOND

253

More cryptic: Enigma machine, version 2
};
}

Add to bottom
text line

package function add(l:String) : Void {
def z:Integer = lines-1;
if(l.equals("\n")) {
var i:Integer = 1;
while(ipaper[i-1].content = paper[i].content;
i++;
}
paper[z].content="";
}
else {
paper[z].content = "{paper[z].content}{l}";
}
}

Push
paper up

Append to
last line

}

The most interesting thing about listing 9.9 is the creation of the sequence paper.
What this code does is to stack several Text nodes using layoutY, scaling each in turn
from a restricted height at the top to full height at the bottom. The rather fearsomelooking code 1.0–((lines-1-i)*0.15) subtracts multiples of 15 percent from the
height of each line, so the first line is scaled the most and the last line not at all. The
result is a curved look to the printout, just what we wanted!
The rest of the listing is unremarkable scene graph code, with the exception of the
add() function at the end. This is what the outside world uses to push new letters onto
the bottom line of the teletype display. Normally the character is appended to the end
of the last Text node, but if the character is a carriage return, each Text node in content is copied to its previous sibling, creating the effect of the paper scrolling up a
line. The last Text node is set to an empty string, ready for fresh output.
We now have all the classes we need for our finished Enigma machine; all that’s left
is to refine the application class itself to include our new graphical rotors and paper.

9.3.3

The Enigma class, version 2: at last our code is ready to encode
Having upgraded the Rotor class and introduced a new Paper class, we need to integrate these into the display. Listing 9.10 does just that.
Listing 9.10 Enigma.fx (version 2, part 1 – changes only)
package jfxia.chapter9;
import
import
import
import
import
import
import
import
import

javafx.scene.Group;
javafx.scene.Node;
javafx.scene.Scene;
javafx.scene.effect.DropShadow;
javafx.scene.layout.Tile;
javafx.scene.layout.VBox;
javafx.scene.paint.Color;
javafx.scene.paint.LinearGradient;
javafx.scene.paint.Stop;

Licensed to JEROME RAYMOND

254

CHAPTER 9

From app to applet

import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
// [Snipped defintions for rotors, reflector, row1, row2,
//
row3 and lamps: see previous version]
def innerWidth:Integer = 450;
def innerHeight:Integer = 520;
var paper:Paper;
// Part 2 is listing 9.11; part 3, listing 9.12

Listing 9.10 is the top part of the updated application class, adding little to what was
already there in the previous version. That’s why I’ve snipped parts of the content
again, indicated (as before) with comments in bold. You can refresh your memory by
glancing back at listing 9.5. As you can see, the window has been made bigger to
accommodate the new elements we’re about to add, and we’ve created a variable to
hold one of them, paper.
The main changes come in the next part, listing 9.11, the scene graph.
Listing 9.11 Enigma.fx (version 2, part 2)
// Part 1 is listing 9.10
Stage {
scene: Scene {
content: Group {
var p:Node;
var r:Node;
var l:Node;
Our new
content: [
Paper class
p = paper = Paper {
width: innerWidth-50;
layoutX: 25;
Rotors in
} ,
horizontal layout
r = Tile {
columns: 3;
layoutX: bind Util.center(innerWidth,r,true);
layoutY: bind p.boundsInParent.maxY;
hgap: 20;
content: [
rotors[0], rotors[1], rotors[2]
];
} ,
l = VBox {
layoutX: bind Util.center(innerWidth,l,true);
layoutY: bind r.boundsInParent.maxY+10;
spacing: 4;
content: [
createRow(row1,false,createLamp) ,
createRow(row2,true,createLamp) ,
createRow(row3,false,createLamp)
];
} ,
VBox {

Licensed to JEROME RAYMOND

More cryptic: Enigma machine, version 2

255

layoutX: bind l.layoutX;
layoutY: bind l.boundsInParent.maxY+10;
spacing: 4;
content: [
createRow(row1,false,createKey) ,
createRow(row2,true,createKey) ,
createRow(row3,false,createKey)
];
effect: DropShadow {
offsetX: 0; offsetY: 5;
};
}
];

Gradient backdrop
};
to window
fill: LinearGradient {
proportional: true; endX: 0; endY: 1;
stops: [
Stop { offset: 0.0; color: Color.BURLYWOOD; } ,
Stop { offset: 0.05; color: Color.BEIGE; } ,
Stop { offset: 0.2; color: Color.SANDYBROWN; } ,
Stop { offset: 0.9; color: Color.SADDLEBROWN; }
];
}
width: innerWidth; height: innerHeight;
}
title: "Enigma";
resizable: false;
onClose: function() { FX.exit(); }
}
// Part 3 is listing 9.12

Listing 9.11 is the meat of the new changes. I’ve preserved the full listing this time,
without any snips, to better demonstrate how the changes integrate into what’s
already there. At the top of the scene graph we add our new Paper class, sized to be 50
pixels smaller than the window width. Directly below that we create a horizontal row
of Rotor objects; recall that the new Rotor is now a CustomNode and can be used
directly in the scene graph. The lamps have now been pushed down to be 10 pixels
below the rotors. At the very bottom of the Scene we install a LinearGradient fill to
create a pleasing shaded background for the window contents.
Just one more change to this class is left, and that’s in the (on screen) keyboard
handler, as shown in listing 9.12.
Listing 9.12 Enigma.fx (version 2, part 3 – changes only)
// Part 1 is listing 9.10; part 2 is listing 9.11
// [Snipped createRow(), createKey(),
//
createLamp(): see previous version]
function handleKeyPress(l:Integer,down:Boolean) : Void {
def res = encodePosition(l);
lamps[res].lit = down;
if(not down) {

Licensed to JEROME RAYMOND

256

CHAPTER 9

From app to applet

var b:Boolean;
b=rotors[2].nextPosition();
if(b) { b=rotors[1].nextPosition(); }
if(b) { rotors[0].nextPosition(); }
}
else {
paper.add(Rotor.posToChr(res));
}

Key down?
Add to paper

}
// [Snipped encodePosition(): see previous version]

At last we have the final part of the Enigma class, and once again the unchanged parts
have been snipped. Check out listing 9.7 if you want to refresh your memory. Only
one change to mention, but it’s an important one: the handleKeyPress() now has
some plumbing to feed its key into the new paper node. This is what makes the
encoded letter appear on the printout when a key is clicked.

9.3.4

Running version 2
Running the Enigma class gives us the display shown in figure 9.10. Our keys and
lamps have been joined by a printout/teletype affair at the head of the window and
three rotors just below.
Clicking the arrows surrounding the rotors causes them to change position, while
clicking the paper display causes it to jump up a line. As we stab away at the keys, our

Figure 9.10 Our Enigma machine
in action, ready to keep all our most
intimate secrets safe from prying
eyes (providing they don’t have
access to any computing hardware
made after 1940).

Licensed to JEROME RAYMOND

From application to applet

257

input is encoded through the rotors, flashed up on the lamps, and appended to
the printout.
Is our Enigma machine finished then? Well, for now, yes, but there’s plenty of
room for new features. The Enigma we developed is a simplified version of the original, with two important missing elements. The genuine Enigma had rotors that could
be removed and interchanged, to further hinder code breaking. It also featured a plug
board: 26 sockets linked by 13 pluggable cables, effectively creating a configurable second reflector. That said, unless anyone reading this book is planning on invading a
small country, the simplified emulator we’ve developed should be more than enough.
In the final part of this chapter we’ll turn our new application into an applet, so we
can allow the world and his dog to start sending us secret messages.

9.4

From application to applet
One of the banes of deploying Java code in the bad old days was myriad options and
misconfigurations one might encounter. Traditionally Java relied on the end user (the
customer) to keep the installed Java implementation up to date, and naturally this led
to a vast range of runtime versions being encountered in the wild. The larger download size of the Java runtime, compared to the Adobe Flash Player, also put off many
web developers. Java fans might note this is an apples-and-oranges comparison; the
JRE is more akin to Microsoft’s .NET Framework, and .NET is many times larger than
the JRE. Yet still the perception of Java as large and slow persisted.
Starting with Java 6 update 10 at the end of 2008, a huge amount of effort was put
behind trying to smarten up the whole Java deployment experience, for both the end
user and the developer. In the final part of this chapter we’ll be exploring some of
these new features, through JavaFX. Although none of them are specifically JavaFX
changes, they go hand in glove with the effort to brighten up Java’s prospects on the
desktop, something that JavaFX is a key part of.

9.4.1

The Enigma class: from application to applet
Having successfully created an Enigma application, we need to port it over to be an
applet. If you’ve ever written any Java applets, this might send a chill down your spine.
Although Java applications and applets have a lot in common, translating one to the
other still demands a modest amount of reworking. But this is JavaFX, not Java, and
our expectations are different. JavaFX was, after all, intended to ease the process of
moving code between different types of user-facing environments.
Listing 9.13 shows the extent of the changes. Astute readers (those who actually
read code comments) will note that once again the unchanged parts of the listing
have been omitted, showing only the new material and its context.
Listing 9.13 Enigma.fx (version 3 – changes only)
package jfxia.chapter9;
import javafx.scene.Group;

Licensed to JEROME RAYMOND

258

CHAPTER 9
import
import
import
import
import
import
import
import
import
import
import
import

From app to applet

javafx.scene.Node;
javafx.scene.Scene;
javafx.scene.effect.DropShadow;
javafx.scene.input.MouseEvent;
javafx.scene.layout.Tile;
javafx.scene.layout.VBox;
javafx.scene.paint.Color;
javafx.scene.paint.LinearGradient;
javafx.scene.paint.Stop;
javafx.scene.shape.Rectangle;
javafx.stage.AppletStageExtension;
javafx.stage.Stage;

Trap mouse
events

Import applet
extension

// [Snipped definitions for rotors, reflector, row1, row2, row3,
//
lamps, innerWidth, innerHeight and paper: see previous version]
Stage {
// [Snipped Scene, title, resizable and
//
onClose: see previous version]
extensions: [
AppletStageExtension {
shouldDragStart: function(e: MouseEvent): Boolean {
return e.shiftDown and e.primaryButtonDown;
}
useDefaultClose: true;
Closing X
}
button
];

Plug extension
into Stage
Condition
to begin
drag action

}
// [Snipped createRow(), createKey(), createLamp(),
//
handleKeyPress() and encodePosition(): see first version]

Your eyes do not deceive you: the entire extent of the modifications amount to nothing
more than eight lines of new code and two extra class imports (only one of which specifically relates to applets). The modifications center on an enhancement to the Stage
object. To accommodate specific needs of individual platforms and environments,
JavaFX supports an extension mechanism, using subclasses of javafx.stage.StageExtension. The specific extension we’re concerned with is the AppletStageExtension,
which adds functionality for using the JavaFX program from inside a web page.
In truth, we’re not obliged to use this extension. Most JavaFX desktop programs
can be run as applets without any specific modifications, but the extension allows
us to specify applet-specific behavior, thus making our application that little bit
more professional.
As you can see from the code, Stage has an extensions property that accepts a
sequence of StageExtension objects. We’ve used an instance of AppletStageExtension, which does two things. First, it specifies when a mouse event should be considered the start of a drag action to rip the applet out of the web page and onto the
desktop. Second, it adds a close box to the applet when on the desktop. But simply
changing our application isn’t enough; for effective deployment on the web we need
to package it into a JAR file. And that’s just what we’ll do next.

Licensed to JEROME RAYMOND