Code essential to being an executable program
package physpics.com.fontviewer;
import javax.swing.*;
public class FontViewer extends JPanel {
. . .
public static void main(String[] args) {
. . .
}
}
Every Java program source file is the declaration of a public class. The
class name is the same as the name of the file. The package statement disambiguates
the class name by slotting it among all the other files from the same vendor;
for FontViewer, the vendor physpics.com
asserts that it will only generate one family of source code in the "fontviewer"
group and that FontViewer is in that group. As a consequence of the class
name and package declaration, we can know that theFontViewer source is
in a file
.../com/physpics/fontviewer/FontViewer.java
FontViewer will be a rectangle on a screen. When I write screen rectangle
objects, I need to decide what data is part of the object and what is part
of the application. It helps if I imagine that my rectangle object may
have more than one instance within an application. As such, I have written
FontViewer as a subclass of JPanel, the Java Swing object that is a rectangle
on screen. To support this, I needed to import at least javax.swing.JPanel.
The Swing methods satisfy my two criteria for an asterisk: they are easily
distinguishable -- all begin with "J" -- and the code will use
many of them. So I wrote
import javax.swing.*
where the asterisk imports all Swing objects.
A Java main program has a public void method main()
.When
I write a class intended as a screen rectangle, I like to test it standalone.
So I write a main()
method in its class. This is okay; a finished
program can have main methods in any or all of its classes; the proper
one must be noted at application run-time. (Either on the command line
or in the jar file's
META-INF/MANIFEST.MF .)
For FontViewer, this file is also the application, so its main()
executes
for both programmer test and user run-time.
Java also supports applets, programs that embed in web pages. It would
seem appropriate that one program file could be executed as either a main
program or an applet, but this is awkward. The main class of an applet
must be a public object that subclasses the Applet class. However, the
design of making FontViewer a screen rectangle meshes nicely with Applet
technolgy. The Applet object for FontViewer need only create a FontViewer
object and display it in the web page.
Java tutorial: A
Closer Look at the "Hello World!" Application
See the sections on the class declaraion and the main method.
Java tutorial: Lesson: Packages
Read the several pages of the lesson.
Outer level comments - not essential for execution, but useful for understanding the program
The "comments" feature tags a few comments. By marking
these comments as a feature, they can be hidden if you
just want to scroll through the code.
Originally I imagined large initial comments, but they didn't appear, so
this feature is small. There are many comments, but they are entwined among
the declarations and tagged according to the feature supported by the declared
variable.
General remarks on comments
Java comments extend from /* to */ or from // to the end of the line. Comments
are important in keeping to the goal of writing a program so it can be revised.
Few programs of any lasting value are ever truly finished. Even now I can
think of several features that "ought" to be added to FontViewer.
Indeed, when I began it simply listed all the fonts. Without comments, the
effort to understand the existing code can be greater than that to write
it in the first place. (This explains why there are so many pieces of code
in the world that do pretty much the same as others.)
One of the early comments describes the screen layout of the application
using a two dimensional array of characters
S C TTT
LLLLLL!
LLLLLL!
LLLLLL!
Where letters differ, they denote different rectangles of the image. Exclamation
marks indicate a scroll bar. I've found that diagrams like this help me describe
all sorts of screen layouts more readbly than text or method
calls. And they lead to simple layouts with BoxLayout
and Box
.
In my photo-tagging
tool, the screen has an image in the upper left, with a map below it; to
the far right are the full tree of defined tags and a list of the tags assigned
to the current image. In between are two rectangles of special purpose tags.
The diagram looks like this:
IIIIII XXXX TTT
IIIIII XXXX TTT
IIIIII XXXX TTT
IIIIII XXXX TTT
IIIIII XXXX TTT
MMMMMMM YY AAAA
MMMMMMM YY AAAA
MMMMMMM YY AAAA
You can decide for yourself if the diagram adds anything to the textual
description. To me the diagram suggests a simple implementation with two
horizontal boxes stacked in a vertical box.
Java is unique in defining a documentation language within special '/**'
or JavaDoc comments. Then the Application Programmer Interface (API)
is described within the program itself. Placed thus, the description has
a better chance of getting updated as the program changes. (Not necessarily
a good chance,
but at least a better one.)
A JavaDoc comment is best thought of as HTML text with special markers for
program documentation. For instance, the diagrams above are between HTML
tags <pre> and </pre> to
preserve the whitespace as written. HTML can also
mark headings, lists, and font changes. Too much HTML, however, detracts
from the readability of the comment in an ordinary program editor, so I tend
to skip it.
Developers writing libraries need to take
care with describing their API. See for instance How
to Write Doc Comments for the Javadoc Tool. For my own application
programs, I use little more than @param
, @return
,
and @throws
. These appear
at the end of the JavaDoc comment for a method or constructor. Each is
at the start of a line possibly following white space with at most one
asterisk in it. The contents of each extends to the next or to the end
of the comment. To write these right (:-), read the sections starting with
the one on @param.
Another useful document is javadoc
- The Java API Documentation Generator. (My code also annotates override
methods as
@Override
because NetBeans otherwise marks the line
as imperfect.)
Java tutorial: A
Closer Look at the "Hello World!" Application
See the section on "Source Code Comments."
The list of all font names available to Java
If we are to list all fonts, we better get that list from somewhere. The "fontlist"
feature gets the list of all fonts available to Java:
static final Font[] fontList; // all fonts known to Java
static {
GraphicsEnvironment gEnv =
GraphicsEnvironment.getLocalGraphicsEnvironment();
fontList = gEnv.getAllFonts();
}
Variable fontList
is "static". It will be computed
only once and shared among all instances of FontViewer, if ever there is more
than one.
There is a design
choice here. There are two methods for getting a list of fonts. One method,
getAvailableFontFamilyNames()
,
gets the names of all font families, Arial, Microsoft Sans Serif,
... . On my system it produces a list of 434 fonts. (Your system will have
fewer if you have not installed cygwin.).
The other method, getAllFonts()
, returns actual Font
objects,
one for each installed font. On my system there
are 741 such fonts. When a family has multiple Fonts, the extras are for Bold,
Italic, and Bold Italic versions. When an application or web page asks for
an italic version of a font, the system checks first whether there is an Italic
member of the font's family; if so, it is used. Otherwise an algorithm is run
to generate an italic version by titling the characters of the plain version.
In the case of Arial, there are actually five families: Arial, Arial Narrow,
Arial Black,
Arial Rounded MT Bold, and Arial Unicode MS. Of these the first two have families
with four members and the others are singleton families. Where the singleton
has an implied slant or weight, sayt with "Oblique" or "Black" in the name,
changing styles in FontViewer will show these fonts as distorted by the algorithm;
the result is often not pretty.
In a more general architecture each FontViewer object would be able to support
a different set of fonts. Such generality would add a bit of code complexity
and contradict the application's goal of viewing all available fonts.
Java tutorial: Physical and Logical Fonts
Non-GUI version - display the font names in the output
Within FontViewer declarations:
static public void printFontList(PrintStream ps)
for (Font f : FontViewer.fontList)
ps.println(f.getFontName());
}
Called from main() with:
public static void main(String[] args) {
FontViewer.printFontList(System.out);
}
printFontList()
is made general by accepting a PrintStream
as an argument. A
client application can then supply any destination. Here the for-statement
iterates over all Font
s in fontList. The qualifier "FontViewer." is
necessary because printFontList()
is static; there is no this and
no FontViewer object is required to call it.
The for-statement in printFontList()
is called a "foreach" statement;
the word
"for" is pronounced "for each" and the colon is pronounced "in".
Be careful writing foreach statements, the syntax varies considerably from
language to language.
You may wonder why the contents of printFontList()
are not simply placed inside
main()
. For a quick one-off program, they should be. I wrote the code as it
is for two contradictory reasons.
First, this way the printing code can remain in the program
code augmenting the interactive version (whether or not printFontList()
is actually
called.) Second the presence of printFontList()
in the code
can be interpreted by the FontList applet to mean that the text list
should be produced instead of the applet. Sadly, to exploit the
second reason, printFontList()
must be absent from interactive
versions.
Java tutorial: The
for Statement
Open a window showing a table listing all fonts
no description
Table column that shows the font name for each row
At the very least, a font viewing system ought to list the fonts. This feature
adds the column of font names to the FontsTable. To do so, it declares array
array of Strings to hold the names, copies each name to the array, and adds
the array as a column of the table.
int fontColIndex = -1;
. . .
final String[] FontColumn
= new String[fontList.length];
for (int inx = 0; inx < fontList.length; inx++) {
String fontname = fontList[inx].getFontName();
FontColumn[inx] = fontname;
}
. . .
fontColIndex = appendColumn("Font Name", FontColumn,
75, 200, 300, getDefaultRenderer(String.class), null);
The width limits of 75, 200, and 300 allow the column to vary somewhat in
width as the window width is changed. In practice, the preference for 200 pixels
means that the font column will stay wide until the window width squeezes all
the columns.
The last two arguments to appendColumn() are the cell renderer and the cell
editor. JTable does not itself paint or edit the contents of each cell. Instead
it overlays that cell with a renderer or editor. It adapts the renderer
to the contents of the cell, gives the renderer the screen size and position
of the cell, and has the renderer do its thing. JTable provides default renderers
for Object, Number, and Boolean, but the one for Object merely calls toString
on the Object and displays the resulting String in a JLabel. When the code
above asks for the default renderer for String.class, JTable provides the one
for Object, a superclass of String. Then the String's toString()
method returns the String itself and that gets painted in the cell.
Editing is similar, but the editor supplied above for the font names column
is null
;
no editing is allowed.
Java tutorial: Setting
and Changing Column Widths
Fundamental code to display a window; builds on the backbone code
Time to show something on the screen! The "window" feature opens a window,
though an empty one. First, the FontViewer constructor puts a border outside
the window and establishes a layout manager:
public FontViewer() {
setBorder(BorderFactory.createEmptyBorder(6, 6, 6, 6));
setLayout(new BorderLayout(6, 6)); // gaps of size 6
}
None of this is strictly necessary, a bare JPanel can be displayed. This code
lays the groundwork for later additions; each of the elements of the window
is added. Although both lines in the constructor refer to Borders,
these are unrelated. The setBorder()
call leaves an empty edge
of six pixels all around the edge of the window. The setLayout()
prepares
the window to have nine sections, like a tic-tac-toe board, where the bulk
of the appication is nominally placed in the center section. FontViewer uses
only the center section and the one above it. (Originallly the sample text
was in the bottom edge section. When I moved it to the top, I could have switched
to a JSplitPane
. Better would have been Box.createVerticalBox()
from
the beginning.)
My scheme for screen rectangle objects is to pretend that there may be multiple
ones in an application. In later features, the FontViewer constructor will
be revised to take an argument, the font categories database. This same database
should be used for all FontViewers, so it is maintained and provided by the
application. For testing (and for the FontViewer application)
, the main()
method
needs to be a rudimentary application. Fortunately, little
code is needed.
final FontViewer subject = new FontViewer();
final JFrame frame = new JFrame(" Font Viewer");
frame.setContentPane(subject);
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
frame.setPreferredSize(new Dimension(725, 800));
frame.pack();
frame.setVisible(true);
}
});
The first line creates an instance of the FontViewer object, the second creates
a screen window, and the third places the FontViewer object into the window.
Within the run()
method the window's size is set, its window-related
fields are initialized, and the window is made visible. (Note the initial space
in the argument to the JFrame() constructor. The argument is the window title.
The space separates the title from the icon that will be added later under
feature "laf".)
After calling invokeLater()
, the program terminates! Its process
ends. Why does the window stay open? Java programs written with java.awt--and
this includes
JPanel
and the other Swing objects--have a separate "event dispatch
thread" (EDT) where window related processing occurs. Runnables passed to invokelater()
are
run on the EDT. Mouse events and keystrokes are fielded
by the EDT. Most of the listener objects are called by the EDT. The EDT runs
only one task at a time, so you are guaranteed that if one listener is active,
none other will be. Tasks passed to invokeLater() are processed between calls
to listeners. So when does the EDT terminate? That is the purpose of the setDefaultCloseOperation()
.
When the window is closed, it is disposed. When the last window is closed, the process ends. (The value EXIT_ON_CLOSE forces an immediate exit. If multiple windows are open, all of them will die as well. Seldom is this what you really want.)
Java tutorial: Using
Top-level Containers
The main Java tutorial on creating Graphical User Interfaces (GUIs) is Learing
Swing with the NetBeans IDE. It is enthusiastic about the Netbeans GUI
design mode. Much as I love NetBeans for coding, the design mode is poor. I
am not a fan. The layout software is buggy and the
resulting programs are difficult to deal with. I believe you will save time
by using Frames and JPanels as I have done in FontViewer. Nonetheless, I often
begin with the NetBeans GUI designer; it lets me see what the user will see
on the screen and helps me imagine what new features are needed or how the
interface can be simplified. After an initial round of development, I can throw
away the NetBeans GUI and switch to direct coding of the GUI components. I
recommend the Box
object as a simple tool for many screen layout tasks.
TODO
How to choose what should be in the object and what in the main():
assume the object will be on the screen in several different places
OR the object will appear with different sets of fonts.
wrapwithJFrame
insertMenuItems
finalsave
Implement scrolling of the table; also provides column headers
With several hundred fonts on most systems, the FontsTable would far exceed the screen; hence scrolling. Java Swing
makes adding scrolling almost painless. To make a view V scrollable, one creates
a JScrollPane and puts V into the scrollpane's "viewport". Beside the viewport,
the scrollpane displays vertical/horizontal scrollbars. Users have learned
how to negotiate scrollbars into scrolling the view.
{Curiously, scrollbars may be flipflopping again on an old controversy. Today,
when one drags down on a scrollbar elevator the text moves up. The scrollbar
background is an analog of the document. This was not always obvious;
early on some developers argued that moving the elevator down should move the
text down, making the scrollbar background the analog of the window. With
the advent of dragging the text to implement scrolling on cell phones and tablets,
the window analog version has been adopted on some platforms. May
confusion merrily reign!}
As this document advances to later features, the scrolling code gets elaborate.
A perfectly workable version is the first. The FontView global declarations
declare the scrollpane as JScrollPane scrollTable;
Then the constructor creates the scrollpane and sets the FontsTable
as the contents of the scrollpane's veiwport. Finally, where mainTable was
added to BorderLayout.CENTER the scrollpane is added instead.
scrollTable = new JScrollPane(
ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS,
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
. . .
scrollTable.setViewportView(mainTable);
. . .
add(scrollTable, BorderLayout.CENTER);
Generally, the code of each Component should be independent of the code of
others. Although Scrollbars inevitably violate this principle. It is to the
credit of the Swing developers that the interdependencies are reasonable. One
dependency is that tables have no headers unless they are within a scrollpane.
Done this way, the scrollpane can implement the headers so they do not scroll;
they are attached above the viewport instead of scrolled as part of the JTable.
Another dependency is that every Component has the methods setAutoScrolls()
and scrollRectToVisible(). If autoscrolls are set true, scrolling happens automatically
as the focus moves between table cells. The more elaborate scrolling features
in FontViewer require turning off autoscrolls for certain operations. The
Component's client can drive scrolling by calling scrollRectToVisible (which
does nothing if the component is not in a scrollpane). ScrollRectToVisible
does not guarantee the the rectangle will occupy anyparticular part of the
visible image. For that clients can call the ViewPort's setViewPosition() method.
Indeed, this is exactly the method that Component calls to implement scrollRectToVisible.
Java tutorial: How to Use ScrollPanes
Table of fonts, categories, and samples
Although the main table of fonts fills most of the FontViewer window, the
FontViewer object simply creates a FontsTable object and adds it as a sub-component:
FontsTable mainTable;
. . .
mainTable = new FontsTable();
. . .
add(mainTable, BorderLayout.CENTER);
The code for FontsTable is far more extensive, occupying about half the source
code. in outline it is this:
private class FontsTable
extends JTable {
DefaultTableModel tableData = new DefaultTableModel() {
// TableModel method
};
// FontsTable constructor
// appendColumn()
// other methods
// classes for renderers and listeners
}
A JTable is a view of some data; the data itself comes from some object with
the TableModel interface. For most tables, the simple approach is to create
an instance of DefaultTableModel, a standard class that implements TableModel.
DefaultTableModel was just about right for FontViewer, but I overrode the isCellEditable
method:
... TableModel method:
public boolean isCellEditable(int row, int col) {
return false;
}
JTable offers a half dozen routes to defining which columns are editable.
After learning them all and finding out where the code first looks, I decided
the simplest and most direct route is to override isCellEditable(). As features
below add editable columns to the table, a line for each is added.
It tests the col value and returns true if that column is editable.
The FontsTable constructor does all the work of setting up the columns and
inserting their data. After some initial parameter setting, the constructor
has three sections where eack column feature must add an item: declaration,
data assignment, and insertion of the column into the model:
... FontsTable constructor
public FontsTable() {
setFillsViewportHeight(true);
setCellSelectionEnabled(true);
setGridColor(new Color(200, 240, 255));
getTableHeader().setBackground(normalHeaderBkgd);
setRowHeight(INITIALFONTSIZE + ADDFORROWHEIGHT);
setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
// declare the object arrays to be the columns
for (int inx = 0; inx < fontList.length; inx++) {
// add data from fontList to the column arrays
}
setAutoCreateColumnsFromModel(false);
setModel(tableData);
// appendColumn for each column
}
Most of the defaults for JTable were suitable for FontsTable, but I did have
to set several, as shown. The appropriate row height turns out to be a constant
number of pixels higher than the fontsize; this number, 8, is the value of
ADDFORROWHEIGHT. The rightmost column is the sample text; it is the most varied
item and is appropriate for resize. In practice, resizing also adjusts some
of the other columns. But anyway, the user can drag column boundaries.
Adding columns to the TableModel and the view requires half a dozen assignment
statements each. I find it more convenient to write and clearer to read if
I introduce a method that just does all those assigments. The values to assign
are the arguments to the method and the compiler checks that I have all the
arguments so all assignments will be done. (One downside is that I have to
assign every parameter, even when the defaults would suffice. The column add
method is:
final int appendColumn(
String name, // column header
Object[]data, // data for the column (usually Strings)
int minW, int prefW, int maxW, // column width constraints
TableCellRenderer render, // object to render each cell
TableCellEditor edit) { // object to edit cell
int index = tableData.getColumnCount();
tableData.addColumn(name, data);
TableColumn col = new TableColumn(index, prefW, render, edit);
col.setMinWidth(minW);
col.setMaxWidth(maxW);
addColumn(col);
return index;
}
The index returned by appendColumn identifies the column. These will change
as the number of features included varies. The variables xxxColIndex are assigned
these values and runtime column tests are against these variables. A line in
isCellEditable() is like this:
if (col == catColIndex) return true;
Note that the code first adds a new column to the model with tableData.addColumn()
and then adds a column to the JTable with plain addColumn. Both are necessary
because setAutoCreateColumnsFromModel() has been set false. I set it false
because the defaults are not quite what I wanted. The JTable view column is
created with the TableColumn constructor listing the the width, renderer, and
editor. The minimum and maximum width are also set. More about renderers is
with the "fonts" feature.
The column widths in all calls to appendColumn() are given as integer constants.
This is highly objectionable stylistically, and will fail on a higher resolution
display device. The right way to do this is by computing widths from font metrics.
I finally decided to simplify the code by writing constants.
Before implementing appendColumn, I explored the several ways to specify the
renderer: override JTable.getCellRenderer(), override
TableColumn.getCellRenderer(), call TableColumn.setCellRenderer(), override
TableModel.getColumnClass(), call TableModel.setColumnClass(), call JTable.setColumnClass(),
call JTable.setDefaultRenderer(). I finally settled on using a parameter to
the TableColumn constructor because it conveniently combined several options
and because I had specialized renderers for most columns.
Another situation where there were many alternatives had an opposite problem:
most approaches failed. The goal was to get some spacing between the column
borders and the ends of the string in the column. The one I had the
most hope for was JTable.getColumnModel().setColumnMargin(). Its failure was
in setting margins that were about half what was specified. What finally worked
was to specify a border around the cell in the cell renderer. Two borders
were used, with a blue border for a cell with focus. These are declared among
the FontViewer global variables, which is where I put constants that affect
appearance:
static final javax.swing.border.Border cellBorder
= BorderFactory.createEmptyBorder(0, 3, 0, 0);
static final javax.swing.border.Border borderBlue
= BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(Color.BLUE),
BorderFactory.createEmptyBorder(0, 2, 0, 0));
Java tutorial: How to Use Tables
TODO
there are no table headers if scrolling is not enabled
viewer.initialFocus in method main
origanlly the code called veiwer.mainTable.requestFocusInWindow; but it is objectionable (and NetBeans raised the objection) to call a method on an object within another. Hence I introduced a method that main can call to get the job done. Indeed that method is now an entry point where a FontViewer object can perform additional tasks when the client is through setting up.
appendColumn() is a helper method designed to avoid repetitive code
setautoresizemode is not NEEDED because we have set size constraints
iscelleditable is required even though null is passed for editor
if do scrollbar w/ mouse, the elevator moves fine
but if next click page up or down,
the scroll reverts to the latest selection
and scrolls via keystroke </p>
<p>set the selection
could cause selection to happen from mouse scrolls
OR
ignore selection when doing key scrolls
--- </p>
<p>PGUP moves seleted line to just off the bottom
then selects the top line
PGDN moves selected line to top
then selects the line at the bottom
NO
revise to
PGUP - select line third from top, then proceed
PGDN - select line third from bottom, then proceed
Add a column to the table showing a text sample in that row's font; add a widget at the top to choose the displayed sample text
no description
An editable TextField to edit the sample text shown in the Samples column of the table
It is not enough to show the names of all fonts; each can be best understood
by viewing a sample in that font. When a user has a specific string
to render, it can be entered in the sampletext widget.
Initially, the text to display should reveal as many text features as possible.
For FontViewer the initial sample text shows all lower-case alphabetics,
two ligatures, and many common digrams and trigrams:
static final String defaultText
= "Fred fixed the zoo flight's problems "
+ "by quickly waiving his objections.";
One source for letter and digram frequencies is
the University of Bristol.
Subsequent global declarations in FontViewer are for a variable to retain
the sample text editor and the current text value:
DemoText sampleEditor;
String currentSample = defaultSample;
At this stage of development, a JTextField suffices instead of DemoText;
defining the DemoText as a subclass prepares the way for later developments.
The currentSample variable contains the value currently displayed in the
sample column; it will differ from the sampleEditor value while the user
is changing the text in sampleEditor.
The FontViewer constructor creates
the text viewing widget:
sampleEditor = new DemoText(defaultText);
. . .
top.add(sampleEditor);
The DemoText object sets a font and
margins:
class DemoText extends JTextField {
public DemoText(String starterText) {
super(starterText);
setFont(labelFont);
setMargin(new Insets(3, 8, 3, 0));
}
}
Exercise: DemoText serves to separate the java code for editing the
sample text from the rest of the FontViewer constructor. Show how to
write the above code as a JTextField initialized directy within the FontViewer
constructor.
Java tutorial: How to Use Text Fields
Table column showing the sample text in this row's font and the current size and style
The rightmost column of the table is the sample text shown in the named
font. Since this is just a string, it is "obvious" that it should
be as easy as showing the name of the font. Nope. Lots more is needed, mostly
when the sample text can change.
The first issue is what object to store as the "value" of the
cell. The string displayed is the same for all rows, so there is no point
in having it as the value. What does change is the font, so that is what
I made the value; for an interesting reason it had to be a subclass of Font.
The renderer, FTFontRenderer, renders fonts by drawing the sample text in
that font. Here is the
basic sample text column machinery:
int sampleColIndex = -1;
. . .
SampleFont[] sampleColumn = new SampleFont[fontList.length];
for (int inx = 0; inx < fontList.length; inx++) {
String fontname = fontList[inx].getFontName();
sampleColumn[inx] = new SampleFont(fontList[inx]);
}
. . .
sampleColIndex = appendColumn("Sample written in named font",
sampleColumn, 75, 300, 10000,
new FTFontRenderer(), null);
. . .
mainTable.refontTheSamples();
The last line initializes all the SampleFont objects to the initial values
of font style and size.
If the value in the sample column was a Font object itself,
copying from the table would not produce the sample text, but would return a
value from Font.toString(). All JComponents implement toString() with a default
that gives the object's name and parameter string. Nothing like what a user
might expect from selecting and copying an instance of the sample text. For
this reason, a subclass of Font is needed that provides its own toString()
method:
class SampleFont extends Font {
public SampleFont(Font f) { super(f); }
public String toString() { return sampleEditor.getText(); }
}
It is always true that the first thing a constuctor does is to invoke the
constructor for the supeerclass to initialize the superclass local fields.
If no arguments need to be passed for the superclass constructor, the subclass
constructor need do nothing special. To pass arguments, as here, the superclass
constructor is called with the syntax super( <superclass constructor arguments>
). Font has indeed a constructor which takes a font as its argument;
the newly constructed Font is a copy of the argument Font. SampleFont has no
local variables, so it does no initialization of its own.
The renderer for SampleFont table cells is indeed similar to FTStringRenderer.
It just gets the string from the sample text instead of from the cell's own
value. That cell value is instead inserted as the font for the cell:
class FTFontRenderer extends DefaultTableCellRenderer {
public Component getTableCellRendererComponent(
JTable table,
Object value, // the SampleFont object
boolean isSelected, boolean hasFocus,
int row, int column) {
setText(currentSample);
setFont((Font)value);
setBorder(hasFocus ? borderBlue : cellBorder);
setForeground(hasFocus ? Color.BLUE : Color.BLACK);
return this;
}
}
Other than setting the text and the font, this class is the same as FTStringRenderer.
Java tutorial: Inheritance (subclasses)
Java tutorial: Overriding and Hiding Methods
The samples code is about the same as that for a column
of strings; buit that is not enough. For the sample column we must also react
as the user edits the sample text via the top widgets: font size, font style,
and sample text. To react to changes in these widgets, we add a listener
to each just after creating it:
sizes.addChangeListener(
new javax.swing.event.ChangeListener() {
public void stateChanged(javax.swing.event.ChangeEvent e) {
mainTable.refontTheSamples();
}
}
);
styles.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent e) {
mainTable.refontTheSamples();
}
});
sampleEditor.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
mainTable.reviseSamples();
}
});
To react to changes the code calls refontTheSamples() and reviseSamples().
These are described in just a bit.
The sampleText is a DemoText object, which is a subclass of JTextField.
As such, it fires ActionPerformed when the ENTER key is typed. In
order to keep the currentSample value up-to-date and to avoid firing unnecessary
ActionPerformed events, I overrode the method that JTextField calls to
begin an ActionPerformed:event:
public void fireActionPerformed() {
String nowText = getText();
if (currentSample.equals(nowText)) return;
currentSample = nowText;
super.fireActionPerformed();
}
So the ActionPerformed event occurs only when the sample text has actually
changed.
For complete
responsiveness, I decided that any changes to the text should also be reflected
to the sample column when focus leaves the DemoText. This requires adding
a FocusListener inside the DemoText constructor; it also calls fireActionPerformed:
public DemoText(String starterText) {
. . .
addFocusListener(new FocusAdapter() {
public void focusLost(FocusEvent e) {
fireActionPerformed();
}
});
}
When the size or style changes, refontTheSamples() is called to create new fonts for the samples column:
private void refontTheSamples() {
int fontsize = sizeModel.getNumber().intValue();
int styleInt = styleValues[styles.getSelectedIndex()];
for (int inx = 0; inx < fontList.length; inx++) {
Font newf = new SampleFont(fontList[inx]
.deriveFont(styleInt, (float)fontsize));
tableData.setValueAt(newf, inx, sampleColIndex);
}
repaint(); // re-render all (to get new samples displayed)
} // end refontTheSamples
When the sample text has changed, trigger a repaint of all
sample cells. Painting will use the currentnSample value.
private void reviseSamples() {
for (int inx = 0; inx < fontList.length; inx++)
tableData.fireTableCellUpdated(inx, sampleColIndex);
}
It is important to understand that there is n magic in event handling.
The work of super.fireActionPerformed is done with a loop over all registered
listeners. The loop simply calls the actionPerformed() methodo in each listener.
In the code above each listener is a unique object dedicated to listening for
events from one source. Before the introductino of anonymous objects,
it was more common to just declare the main object, in this case FontViewer,
as implementing ActionListener.Then among the object's methods there would
have to be actionPerformed(). That maim object would then itself be registered
as a listener. If action events could come from multiple sources, the actionPerformed()
method could test the Event's .source field to determine what object initiated
the call. (Later on, FontViewer will be seen to implement actionPeerformed()
to handle menu selection events.
Java tutorial: How
to Write an Action Listener
The top section of the screen image, holds the sizer and styler fields; uses a layout manager other than than main one
The top feature creates the top portion of the window, containing the widgets
for setting the sample text: size, style, and the the text itself. (The actual
widgets are created by the next three features.)
Box top = Box.createHorizontalBox();
top.add(sizes);
top.add(Box.createHorizontalStrut(12));
top.add(styles);
top.add(Box.createHorizontalStrut(12));
top.add(sampleEditor);
top.add(Box.createHorizontalStrut(12));
add(top, BorderLayout.NORTH);
The first line creates a layout box; a simple container that places its
contents one after the other. The middle lines add the widgets, surrounding
each with horizontal struts to space between them. The last line inserts
the completed Box as the component at the top of the FontViewer; that is,
at the top of the JPanel of which FontViewer is a subclass. The horizontal
glue objects will expand or contract as the Box's width is changed. If a
widgets' minimum width is less than its maximum, some of the excess width
will also be shared with that widget.
Method add() is common to all Container objects like JFrame and Box. The
Component argument is inserted at the end of the Container's list of childerLayout
include NORTH, SOUTH, EAST, WEST, and CENTER.
Java tutorial: How
to Use BoxLayout
Add top widgets to choose size and style of the text samples; add a checkbox column; add sorting to bring the checked rows to the top
no description
Adds a column of checkboxes; sorting on it brings candidates together
Originally I introduced the "category" column for
the user to make notes about fonts for a task. That column
evolved into more permanent category information, raising once
again the need for a way to flag candidate fonts for a particular pupose..
My solution was to invent a simple check-off column; one click adds the font
and sorting on the column brings all candidates together. This became the ""Ck" column.
JTable has direct support for columns of checkmarks, so the column was easy
to implement.
The code is in the FontTable globals and constructor:
int chkColIndex = -1;
. . .
Boolean[] chkColumn = new Boolean[fontList.length];
for (int inx = 0; inx < fontList.length; inx++) {
chkColumn[inx] = new Boolean(false);
}
chkColIndex = appendColumn("Ck", chkColumn,
30, 30, 30, getDefaultRenderer(Boolean.class),
getDefaultEditor(Boolean.class));
The built-in default renderers for Booleans display a JCheckBox and toggle
it for a mouse click (or for the space bar when the checkbox has the focus).
The checkbox column is editable as indicated in isCellEditable()
:
if (col == chkColIndex) return true;
Java tutorial: JTable:
Editors and Renderers , How
to Use Checkboxes
A JSpinner to choose the size for the font sample text
The"picksize" feature adds a JSpinner to the top row for choosing
the size of text to display. Among the declarations is INITIALFONTSIZE so the
spinner and the initial display can both have the same size. The sizeModel
describes the range of values the JSpinner can choose among; it is passed to
the contructor that creates sizes, the JSpinner object.
final static int INITIALFONTSIZE = 12; // fontsize for
samples SpinnerNumberModel sizeModel // font sizes range
= new SpinnerNumberModel(INITIALFONTSIZE, 6, 40, 4);
JSpinner sizes; // will range over sizeModel
In FontViewer, the JSpinner is constructed and given a ChangeListener to respond
to changes in the value. As with the styler object, the response is to recreate
the displayed samples to show them in the new size.
sizes = new JSpinner(sizeModel);
sizes.setFont(labelFont);
sizes.addChangeListener(new javax.swing.event.ChangeListener() {
public void stateChanged(javax.swing.event.ChangeEvent e) {
mainTable.refontTheSamples();
}
});
. . .
top.add(sizes);
Within refontTheSamples(), the current size value is retrieved from the sizeModel:
int fontsize = sizeModel.getNumber().intValue();
In an effort to be general, SpinnerNumberModel records and returns its value
as a Number object; it supports floating values as well as integers. SpinnerNumberModel.getNumber()
returns a Number, so an additional call to intValue() is needed to
retrieve the actual value of the spinner.
Although SpinnerNumberModel is a class, SpinnerModel is not; it is an interface.
Any class can "implement
" an interface; it must implement
all the methods defined by the interface. If AnyClass
is a nested
class, it can be
static
if it has no need to refer to values in its enclosing class:
public static class AnyClass implements SpinnerModel
{ public void addChangeListener(ChangeListener l) { ... }
public Object getNextValue() { ... }
public Object getPreviousValue() { ... }
public Object getValue() { ... }
public void removeChangeListener(ChangeListener l) { ... }
public void setValue(Object value) { ... }
}
Exercise: Implement AnyClass
so it is a SpinnerModel
that
ranges over roman numerals from I to X. Setting an illlegal value should throw
IllegalArgumentException
. The simplest approach may be to have
an array of the ten valid values. Note that ChangeListener is any Object that
has a method "void stateChanged(ChangeEvent e)
". This method must be called
from setValue()
.
Java tutorial: How to Use Spinners
A JComboBox to choose bold/italic for the font sample text
The pickstyle widget is a JComboBox for choosing among four font "styles": plain,
bold, italic, or bold/italic. It displays the current style and offers a drop
menu to choose among all four styles. The widget code begins with declarations
of the syles and the widget itself:
static final String[] styleNames // for the styler JComboBox
= {"Plain", "Bold", "Italic", "Bold Italic"};
static final int[] styleValues
= new int[]{Font.PLAIN, Font.BOLD,
Font.ITALIC, Font.BOLD | Font.ITALIC};
JComboBox styles = new JComboBox(styleNames);
In the FontViewer constructor, the pickstyle JComboBox is adapted by setting
the font it uses to display the style names and by adding a listener. The listener's
itemStateChanged method is called whenever the style changes. It calls refontTheSamples
to change the displayed sample texts. This is the same method that is called
by the listener for the fontsize selector widget.
static final Font labelFont = new Font("Dialog", Font.PLAIN, 14);
styles.setFont(labelFont);
styles.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent e) {
mainTable.refontTheSamples();
}
}); . . . top.add(styles);
Within refontTheSamples(), the style value is retrieved with
int styleInt = styleValues[styles.getSelectedIndex()];
this value is then used in setting the fonts for the samples on all visible
lines.
Here styleValues and styleNames are two "parallel" arrays; values
at the same index are related. Ordinarily I prefer to store related values
as fields in objects; perhaps StylePair objects, where each StylePair has
a name and a value. To do so would require writing a new class and a custom
renderer
for the JComboBox; parallel arrays are much simpler.
JComboBox's method addItemListener() takes as argument an object of type
ItemListener. One could be created by declaring an innerclass within FontViewer:
private void class StylerItemListener implements ItemListener
{ . . .
}
This class would have one method, itemStateChanged(ItemEvent
e), as required by the ItemListener interface declaration. The call
to addItemListener() would then be
addItemListener(new StyleItemListener());
Instead of declaring a nested class and referring to it by name, the actual
code declares the listener as an anonymous class. The body of the
class is written within a pair of curly braces following the arguments to
the constructor. (When writing listeners, there are seldom arguments to
the constructor.) The body of the class is generally one or more method
declarations like that of itemStateChanged().
Java tutorial: How
to Use Combo Boxes
Java tutorial: Nested
Classes
Java tutorial: Creating
Anonymous Classes
Java in a Nutshell: 3.12.
Anonymous Classes
Includes a
good description of restrictions on anonymous classes.
The very simplest code for allowing sorting on columns (Hidden if the "sort" feature is visible)
no description
moving columns triggers sort;
sigh - need sort buttons
Sort the table on the category or fontname column
to do per-column tooltips, the most general method
is to create a cell-renderer and assign it to a header
with jtable.getColumn(columnNumber).setHeaderRenderer(specialRenderer)
lambda
Lambda QuickStart guide, Lambda Best Practices
Add File and Help menus; add distinct tool tips for each column
no description
Menu item to show information about the application and its authors
The "About" menu item is where the development team gets to say whatever they want about the app. Who wrote it. What does it do. What is the current status. How many pizzas fueled the development. For future sanity, the about box must also contain a version number and the date of the release.
The About
item is added to the Help
menu with an action created by FontViewer's createAction method:
// HELP->ABOUT FONTVIEWER
helpMenu.add(createAction(FontViewer.this,
"About FontViewer",
"FontViewer version and info on categories file",
null,
()->{ about(); }
));
The about()
call reveals the about box via JOptionPane
. First it creates the message as a string of HTML code and puts it in a JLabel
. Then this is passed to showMessage()
. In FontViewer (without the telluser
feature) the steps are
JLabel lblmsg = new JLabel("<html>" + msg + "</html>");
JOptionPane.showMessageDialog(this, lblmsg);
For the msg
value to include the version, that value is fetched from the .jar
file. This avoids having to keep the version number in more than one place:
String version = getClass().getPackage()
.getImplementationVersion();
Then the message is constructed with
String msg = "<a href='"+SITE_URL+"/'>FontViewer</a>"
+" version " + version
+ " by ZweiBieren@PhysPics.com";
Additional message lines are added by code from other features.
Altogether, and with the telluser
feature, the FontViewer about
box looks like this.