> Java > apps > fontviewer > explore
> Java > apps > fontviewer > explore
by ZweiBieren Explore FontViewer.java Java coffee cup logo
Section Tip
Help
Grab URL
Copied
× Headers
Entify
Select ALL























































// – FontViewer preface : comments, package, imports
/** FontViewer.java  //  2   backbone
* Show all the fonts that Java knows  //  3   backbone
* and display a sample string in each  //  4   backbone
*  //  5   backbone
* @author zweibieren  //  6   backbone
* Jan 13, 2010 5:25 PM  //  7   backbone
*/  //  8   backbone
  //  9   backbone
package com.physpics.apps.fontviewer;  //  10   backbone
  //  11   backbone
import com.physpics.tools.IOUtils;  //  12   catsmap
import com.physpics.tools.StringMap;  //  13   catsmap
import java.awt.*;  //  14   backbone
import java.awt.event.*;  //  15   listwindow
import java.io.*;  //  16   backbone
import java.net.MalformedURLException;  //  17   logoimages
import java.net.URI;  //  18   logoimages
import java.net.URISyntaxException;  //  19   logoimages
import java.net.URL;  //  20   logoimages
import java.util.*;  //  21   backbone
import javax.swing.*;  //  22   backbone
import javax.swing.table.*;  //  23   table
import javax.swing.text.DefaultEditorKit;  //  24   ccpaction
/**  //  25   backbone
* View all fonts on the system.  //  26   backbone
* The window is laid out like this  //  27   listwindow
* <pre>  //  28   listwindow
* S C TTT  //  29   listwindow
* LLLLLL!  //  30   listwindow
* LLLLLL!  //  31   listwindow
* LLLLLL!  //  32   listwindow
*  //  33   listwindow
* S, a JSpinner for choosing a font size  //  34   listwindow
* C, a JComboBox for choosing one of the four styles  //  35   listwindow
* TT, A JTextField for entering the text to be displayed in the fonts  //  36   listwindow
* LLL, a JTable where each row has:  //  37   listwindow
* "B" if font is bold  //  38   listwindow
* "I" if font is italic  //  39   listwindow
* font category, e.g., script or serif  //  40   listwindow
* font name  //  41   listwindow
* a check box to mark candidate fonts  //  42   listwindow
* the T text displayed in the named font  //  43   listwindow
* ! there is a vertical scrollbar for LLL  //  44   listwindow
* </pre>  //  45   listwindow
*/  //  46   backbone
public class FontViewer extends JPanel {  //  47   backbone
// – ---------- end FontViewer preface ----------
// – – FontViewer variables : globals for the visible window components
static final Font[] FONT_LIST; // all Fonts known to local Java  //  50   fontlist
static {  //  51   fontlist
GraphicsEnvironment gEnv =  //  52   fontlist
GraphicsEnvironment.getLocalGraphicsEnvironment();  //  53   fontlist
FONT_LIST = gEnv.getAllFonts();  //  54   fontlist
}  //  55   fontlist
  //  56   fontlist
static final boolean IS_IN_WEBSTART;  //  57   webstart
static {  //  58   webstart
boolean isin=false;  //  59   webstart
try { Class.forName("javax.jnlp.ServiceManager");  //  60   webstart
isin = true;  //  61   webstart
} catch (ClassNotFoundException ex) { }  //  62   webstart
IS_IN_WEBSTART = isin;  //  63   webstart
}  //  64   webstart
  //  65   webstart
static final Toolkit TOOLKIT = Toolkit.getDefaultToolkit();  //  66   backbone
static final java.awt.datatransfer.Clipboard CLIPBOARD  //  67   fillcut
= TOOLKIT.getSystemClipboard();  //  68   fillcut
static final String SITE_URL  //  69   backbone
= "http://physpics.com/Java/apps/fontviewer";  //  70   backbone
  //  71   backbone
static final Image LOGO16, LOGO32, LOGO64;  //  72   logoimages
static Image getImage(String name) {  //  73   logoimages
URL imageURL = FontViewer.class.getResource(  //  74   logoimages
"/com/physpics/apps/fontviewer/images/"+name);  //  75   logoimages
if (imageURL == null) { // not fully installed: try fetch from web  //  76   logoimages
try {  //  77   logoimages
imageURL = new URI(SITE_URL+"/images/"+name).toURL();  //  78   logoimages
} catch (URISyntaxException | MalformedURLException ex) {  //  79   logoimages
imageURL = null;  //  80   logoimages
}  //  81   logoimages
}  //  82   logoimages
return (imageURL == null ? null : TOOLKIT.createImage(imageURL));  //  83   logoimages
}  //  84   logoimages
static {  //  85   logoimages
LOGO16 = getImage("FV16.png");  //  86   logoimages
LOGO32 = getImage("FV32.png");  //  87   logoimages
LOGO64 = getImage("FV64.png");  //  88   logoimages
}  //  89   logoimages
static final ImageIcon ICON64  //  90   logoimages
= (LOGO64 == null ? null : new ImageIcon(LOGO64));  //  91   logoimages
  //  92   logoimages
// borders for cells in the table  //  93   table
static final javax.swing.border.Border CELL_BORDER  //  94   table
= BorderFactory.createEmptyBorder(0, 3, 0, 0);  //  95   table
static final javax.swing.border.Border BORDER_BLUE  //  96   table
= BorderFactory.createCompoundBorder(  //  97   table
BorderFactory.createLineBorder(Color.BLUE),  //  98   table
BorderFactory.createEmptyBorder(0, 2, 0, 0));  //  99   table
  //  100   table
static final Font LABEL_FONT // default font for screen text  //  101   listwindow
= new Font("Dialog", Font.PLAIN, 14);  //  102   listwindow
static final Font STYLES_FONT  //  103   listwindow
= new Font("Serif", Font.PLAIN, 14);  //  104   listwindow
static final Font HEADER_FONT // font for column headers  //  105   table
= new Font("Dialog", Font.BOLD, 14);  //  106   table
static final Font BUTTON_FONT // font for dialog buttons  //  107   adjustlaf
= new Font("Dialog", Font.BOLD, 14);  //  108   adjustlaf
static final String[] STYLE_NAMES // for the style JComboBox  //  109   pickstyle
= {"Plain", "Bold", "Italic", "Bold Italic"};  //  110   pickstyle
static final int[] STYLE_VALUES  //  111   pickstyle
= new int[] {Font.PLAIN, Font.BOLD,  //  112   pickstyle
Font.ITALIC, Font.BOLD | Font.ITALIC};  //  113   pickstyle
final static int INITIALFONTSIZE = 12; // initial fontsize for samples  //  114   listwindow
// a row is offscreen when no more than SLIVER pixels are visible  //  115   rowvis
final static int SLIVER = 6;  //  116   rowvis
// table row height is fontsize plus ADDFORROWHEIGHT  //  117   table
final static int ADDFORROWHEIGHT = 8;  //  118   table
static final String DEFAULT_SAMPLE // default string for fontsamples  //  119   listwindow
= "The quick brown fox filled a theater "  //  120   listwindow
+ "playing his father's jazz improvisations.";  //  121   listwindow
// This string has all letters, a ligature, an aposrophe, and a period.  //  122   listwindow
// It includes common di- & trigrams: ed fi her ing is re ter the tion  //  123   listwindow
// but lacks other common di- & trigrams: and ent es for hat of to  //  124   listwindow
  //  125   listwindow
public static final String CATS_FILE = "fontcategories.txt";  //  126   catsmap
public static final String DEFAULT_CATSMAP  //  127   catssave
= "userhome:/FontViewer/" + CATS_FILE;  //  128   catssave
public static final String HELP_URL = SITE_URL + "/FVHelp.html";  //  129   guide
  //  130   listwindow
// components of the window  //  131   listwindow
final JComboBox<String> styles = new JComboBox<>(STYLE_NAMES);  //  132   pickstyle
final SpinnerNumberModel sizeModel // font sizes range  //  133   picksize
= new SpinnerNumberModel(INITIALFONTSIZE, 6, 40, 4);  //  134   picksize
final JSpinner sizes; // choose from sizeModel  //  135   picksize
JTextField sampleEditor;  //  136   seetext
SampleText sampleEditor; // editor for the sample text  //  137   seetext
  //  138   picksize
// the fonts listing  //  139   table
FontsTable mainTable; // one line per font  //  140   table
final JScrollPane scrollTable; // scroller for mainTable  //  141   scroll
int styleColIndex = -1; // STYLECOL index for style flags, B/I  //  142   styles
int catColIndex = -1; // CATCOL index for categories  //  143   categories
int fontColIndex = -1; // FONTCOL index for font names  //  144   fonts
int chkColIndex = -1; // CHECKCOL index for checkboxes  //  145   checkboxes
int sampleColIndex = -1; // SAMPLECOL index for sample text  //  146   seetext
final String[] columnToolTips = new String[12];  //  147   coltooltips
  //  148   table
// Category map : from font names to category text  //  149   catsmap
StringMap catsMap = new StringMap(); // data for the categories column  //  150   catsmap
ArrayList<String> categorySources  //  151   about
= new ArrayList<>(); // for 'about' dialog  //  152   about
Action saveAction, saveasAction;  //  153   catssave
// – – ---------- end FontViewer variables ----------
// – – FontViewer - LifeCycle methods : create, focus, cleanup
// – – – FontViewer() constructor : create the window components
/** Construct a FontViewer.  //  157   listwindow
* Instead of calling this constructor,  //  158   listwindow
* call FontViewer.create {@link FontViewer#create()}  //  159   listwindow
*/  //  160   listwindow
private FontViewer() {  //  161   listwindow
// begin autosaving the category data  //  162   catssave
catsMap.setAutosaving(true);  //  163   catssave
// make the size chooser for the top row  //  164   picksize
sizes = new JSpinner(sizeModel);  //  165   picksize
sizes.setFont(LABEL_FONT);  //  166   picksize
sizes.addChangeListener(e->mainTable.refontTheSamples());  //  167   seetext
  //  168   picksize
// make the style chooser for the top row  //  169   pickstyle
styles.setFont(STYLES_FONT);  //  170   pickstyle
styles.addItemListener(e->mainTable.refontTheSamples());  //  171   seetext
  //  172   pickstyle
// make the table showing fonts  //  173   table
scrollTable = new JScrollPane(  //  174   scroll
ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS,  //  175   scroll
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER) {  //  176   scroll
};  //  177   scroll
mainTable = createFontsTable();  //  178   table
scrollTable.setViewportView(mainTable);  //  179   scroll
scrollTable.setCorner(ScrollPaneConstants.UPPER_RIGHT_CORNER,  //  180   scrollcorner
new JPanel(){  //  181   scrollcorner
{ setOpaque(true); setBackground(mainTable.normalHeaderBkgd); }  //  182   scrollcorner
@Override  //  183   scrollcorner
protected void paintBorder(Graphics g) {  //  184   scrollcorner
g.drawLine(0, getHeight() - 1,  //  185   scrollcorner
getWidth(), getHeight() - 1);  //  186   scrollcorner
}  //  187   scrollcorner
});  //  188   scrollcorner
mainTable.reviseSamples();  //  189   seetext
mainTable.refontTheSamples();  //  190   seetext
} // end FontViewer(...)  //  191   listwindow
// – – – ---------- end FontViewer() constructor ----------
// – – – FontViewer create() : Construct and initialize a FontViewer
/** Construct a FontViewer and initialize.  //  194   listwindow
* This method avoids the need to call methods  //  195   listwindow
* of a partially constructed FontViewer.  //  196   listwindow
* @return the created FontViewer  //  197   listwindow
*/  //  198   listwindow
public static FontViewer create() {  //  199   listwindow
FontViewer viewer = new FontViewer();  //  200   listwindow
// make the sample text editor  //  201   edittext
viewer.sampleEditor = new JTextField(DEFAULT_SAMPLE);  //  202   seetext
viewer.sampleEditor = new JTextField(DEFAULT_SAMPLE);  //  203   edittext
viewer.sampleEditor = viewer.createSampleText(DEFAULT_SAMPLE);  //  204   seetext
// ENTER key in sampleEditor : so rebuild the SAMPLECOL  //  205   seetext
viewer.sampleEditor.addActionListener(  //  206   seetext
e ->viewer.mainTable.reviseSamples());  //  207   seetext
  //  208   seetext
// put things in the window  //  209   listwindow
viewer.setBorder(BorderFactory.createEmptyBorder(6, 6, 6, 6));  //  210   listwindow
viewer.setLayout(new BorderLayout(6, 6)); // gaps of size 6  //  211   listwindow
Box top = Box.createHorizontalBox();  //  212   top
top.add(Box.createHorizontalStrut(12));  //  213   top
viewer.sizes.setBorder(BorderFactory.createTitledBorder("Font Size"));  //  214   picksize
top.add(viewer.sizes);  //  215   picksize
top.add(createTitledWidget("Font Size", viewer.sizes, true));  //  216   titled
top.add(Box.createHorizontalStrut(12));  //  217   top
viewer.styles.setBorder(BorderFactory.createTitledBorder("Font Style"));  //  218   pickstyle
top.add(viewer.styles);  //  219   pickstyle
top.add(createTitledWidget("Font Style", viewer.styles, true));  //  220   titled
top.add(Box.createHorizontalStrut(12));  //  221   top
top.add(viewer.sampleEditor);  //  222   edittext
top.add(createTitledWidget("Sample Text",  //  223   titled
viewer.sampleEditor, false));  //  224   titled
top.add(Box.createHorizontalStrut(12));  //  225   top
  //  226   top
viewer.add(top, BorderLayout.NORTH);  //  227   top
viewer.add(viewer.mainTable, BorderLayout.CENTER);  //  228   table
viewer.add(viewer.scrollTable, BorderLayout.CENTER);  //  229   scroll
return viewer;  //  230   listwindow
} // end create(...)  //  231   listwindow
// – – – ---------- end FontViewer create() ----------
// – – – FontViewer printFontList() : print fontnames
/**  //  234   offline
* Print the list of fonts  //  235   offline
*  //  236   offline
* @param ps where to send the output  //  237   offline
*/  //  238   offline
static public void printFontList(PrintStream ps) {  //  239   offline
for (Font f : FontViewer.FONT_LIST)  //  240   offline
ps.println(f.getFontName());  //  241   offline
}  //  242   offline
// – – – ---------- end FontViewer printFontList() ----------
// – – – FontViewer closePanel() : Save before kill window
/** Check that category values are saved before closing window.  //  245   shut
* @return True to proceed with window closure  //  246   shut
*/  //  247   shut
public boolean closePanel() {  //  248   shut
mainTable.finishEditing();  //  249   finishedit
return true; // always exit  //  250   shut
if (catsMap.flush()) // no pending change : exit  //  251   catsshut
return true;  //  252   catsshut
JButton help = new JButton("Help");  //  253   catsshut
help.setFont(BUTTON_FONT);  //  254   adjustlaf
help.addActionListener(butev->browseGuide("categoriesfile"));  //  255   catsshut
Object[] buttons = new Object[]{"Save & Exit",  //  256   catsshut
"Exit without save", "Do not exit", help};  //  257   catsshut
JTextField dest = new JTextField(DEFAULT_CATSMAP, 40);  //  258   catsshut
int opt = talkToUser("Unsaved Category Data",  //  259   catsshut
"Data unsaved. Would you like to store it? If so, where?",  //  260   catsshut
dest, buttons);  //  261   catsshut
if (opt == 1) return true; // exit wo/ save  //  262   catsshut
if (opt != 0) return false; // other cases, do not exit  //  263   catsshut
// case 0, try to save  //  264   catsshut
String msg = setSaveFile(dest.getText());  //  265   catsshut
if (msg != null) {  //  266   catsshut
tellUser("Final categories save failed", msg);  //  267   telluser
return false;  //  268   catsshut
}  //  269   catsshut
return true;  //  270   catsshut
}  //  271   shut
// – – – ---------- end FontViewer closePanel() ----------
// – – – FontViewer wrapWithJFrame() : create a JFrame and insert the viewer in it
/**  //  274   listwindow
* Make this FontViewer be a standalone application.  //  275   listwindow
* Add icons, menus, and close-window behavior.  //  276   listwindow
* @return A new JFrame holding this FontViwer  //  277   listwindow
*/  //  278   listwindow
public JFrame wrapWithJFrame() {  //  279   listwindow
final JFrame frame = new JFrame(" Font Viewer");  //  280   listwindow
frame.setContentPane(this);  //  281   listwindow
if (LOGO64 != null) {  //  282   logoimages
java.util.ArrayList<Image> icons = new java.util.ArrayList<>();  //  283   logoimages
icons.add(LOGO16);  //  284   logoimages
icons.add(LOGO32);  //  285   logoimages
icons.add(LOGO64);  //  286   logoimages
frame.setIconImages(icons);  //  287   logoimages
}  //  288   logoimages
JMenuBar menuBar = new JMenuBar();  //  289   menu
insertMenuItems(menuBar);  //  290   menu
frame.setJMenuBar(menuBar);  //  291   menu
// Deal with external window closure  //  292   listwindow
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);  //  293   listwindow
frame.setDefaultCloseOperation(  //  294   shut
WindowConstants.DO_NOTHING_ON_CLOSE);  //  295   shut
frame.addWindowListener(new WindowAdapter() {  //  296   shut
@Override  //  297   shut
public void windowClosing(WindowEvent we) {  //  298   shut
if (closePanel())  //  299   shut
we.getWindow().dispose();  //  300   shut
}  //  301   shut
});  //  302   shut
return frame;  //  303   listwindow
}  //  304   listwindow
// – – – ---------- end FontViewer wrapWithJFrame() ----------
// – – ---------- end FontViewer - LifeCycle methods ----------
// – – FontViewer enclosed classes : pieces of the visible window
////////////////////////////////  //  308   top
// FONTVIEWER ENCLOSED CLASSES  //  309   top
// – – – TitledWidget class : A JPanel with a title at top and a widget within
/**  //  311   titled
* Create a JPanel containing a widget with a title above it.  //  312   titled
* This class is here because look-and-feel implementations  //  313   titled
* differ too much in borders around JComboBox and JSpinner.  //  314   titled
*/  //  315   titled
public static class TitledWidget extends JPanel {  //  316   titled
/**  //  317   titled
* Create a TitledWidget  //  318   titled
*/  //  319   titled
private TitledWidget() {  //  320   titled
}  //  321   titled
}  //  322   titled
/**  //  323   titled
* Create a TitledWidget with padding on the right and below.  //  324   titled
*  //  325   titled
* @param t Title string to display  //  326   titled
* @param widget The widget to border  //  327   titled
* @param fixedWidth Prevent width change  //  328   titled
* @return the new widget  //  329   titled
*/  //  330   titled
public static TitledWidget createTitledWidget(String t,  //  331   titled
JComponent widget, boolean fixedWidth) {  //  332   titled
TitledWidget titler = new TitledWidget();  //  333   titled
titler.setLayout(new BorderLayout()); // layout for JPanel  //  334   titled
JLabel lbl = new JLabel(" " + t); // to line up left edges  //  335   titled
lbl.setForeground(Color.BLUE);  //  336   titled
titler.add(lbl, BorderLayout.NORTH);  //  337   titled
titler.add(widget, BorderLayout.CENTER);  //  338   titled
if (fixedWidth) {  //  339   titled
Dimension wpref = widget.getPreferredSize();  //  340   titled
Dimension lpref = lbl.getPreferredSize();  //  341   titled
Dimension pref = new Dimension(  //  342   titled
Math.max(wpref.width, lpref.width),  //  343   titled
wpref.height + lpref.height);  //  344   titled
titler.setMaximumSize(pref);  //  345   titled
}  //  346   titled
return titler;  //  347   titled
}  //  348   titled
// – – – ---------- end TitledWidget class ----------
// – – – SampleText class : a JTextField with application-specific event handlers and a reset Action
  //  351   edittext
/** Construct and initialize a SampleText. This method avoids  //  352   edittext
* calling methods on partially constructed objects.  //  353   edittext
* @param starterText initial text displayed  //  354   edittext
* @return the SampleText  //  355   edittext
*/  //  356   edittext
public SampleText createSampleText(String starterText) {  //  357   edittext
final SampleText text = new SampleText(starterText);  //  358   edittext
text.setFont(LABEL_FONT);  //  359   edittext
text.setMargin(new Insets(3, 8, 3, 0));  //  360   edittext
  //  361   edittext
// focus loss from edittext : so rebuild all samples in table  //  362   seetext
text.addFocusListener(new FocusAdapter() {  //  363   seetext
@Override  //  364   seetext
public void focusLost(FocusEvent e) {  //  365   seetext
text.fireActionPerformed();  //  366   seetext
}  //  367   seetext
});  //  368   seetext
// define an Action that will reset the sample text  //  369   resettext
text.resetAction = createAction(FontViewer.this, "_Reset sample",  //  370   resettext
"Reset the sample text to its original value",  //  371   resettext
null, // no window-wide accelerator key  //  372   resettext
e->{ text.setText(DEFAULT_SAMPLE);  //  373   resettext
text.fireActionPerformed(); } // revise samples  //  374   resettext
);  //  375   resettext
// install an accelerator key restricted to this sample text  //  376   resettext
KeyStroke ctlR = KeyStroke.getKeyStroke("control pressed R");  //  377   resettext
String resetCommand = "reset sample text";  //  378   resettext
text.getInputMap(JComponent.WHEN_FOCUSED).put(ctlR, resetCommand);  //  379   resettext
text.getActionMap().put(resetCommand, text.resetAction);  //  380   resettext
  //  381   resettext
// define and utilize a popup menu with only an item for reset  //  382   resettext
JPopupMenu resetMenu = new JPopupMenu();  //  383   resettext
JMenuItem resetItem = new JMenuItem(text.resetAction);  //  384   resettext
resetItem.setAccelerator(ctlR); // display "Ctrl+R" in menu  //  385   resettext
resetMenu.add(resetItem);  //  386   resettext
text.setComponentPopupMenu(resetMenu);  //  387   resettext
  //  388   edittext
return text;  //  389   edittext
}  //  390   edittext
/**  //  391   edittext
* A viewer/editor for the sample text to be shown in each font.  //  392   edittext
* Little more than a JTextField with an Action to reset the text  //  393   edittext
* to its default value.  //  394   edittext
*/  //  395   edittext
public class SampleText extends JTextField {  //  396   edittext
public Action resetAction = null;  //  397   resettext
String currentSample;  //  398   seetext
  //  399   seetext
/** Override firing of actionPerformed  //  400   seetext
* so it only fires when the text has actually changed.  //  401   seetext
*/  //  402   seetext
@Override  //  403   seetext
protected void fireActionPerformed() {  //  404   seetext
if (getText().equals(currentSample)) return;  //  405   seetext
currentSample = getText();  //  406   seetext
super.fireActionPerformed();  //  407   seetext
}  //  408   seetext
/**  //  409   edittext
* Internal constructor for SampleText. Instead of calling this,  //  410   edittext
* clients should invoke FontViewer.createSampleText.  //  411   edittext
* @param starterText initial text to display in box  //  412   edittext
*/  //  413   edittext
private SampleText(String starterText) {  //  414   edittext
super(starterText, 50);  //  415   edittext
currentSample = starterText;  //  416   seetext
}  //  417   edittext
}  //  418   edittext
// – – – ---------- end SampleText class ----------
// – – – FontViewer createFontsTable() : Create the table and add its columns
///////////////////////////////////////  //  421   table
// FontsTable  //  422   table
  //  423   table
/** @return the main font table */  //  424   table
public FontsTable getMainTable() { return mainTable; }  //  425   table
  //  426   table
/**  //  427   table
* Create and initialize a FontsTable.  //  428   table
* (This method avoids calling overridable methods from the constructor.)  //  429   table
* @return the initialized FontsTable  //  430   table
*/  //  431   table
private FontsTable createFontsTable() {  //  432   table
FontsTable table = new FontsTable();  //  433   table
table.setFillsViewportHeight(true);  //  434   table
table.setCellSelectionEnabled(true);  //  435   table
table.setGridColor(new Color(200, 240, 255));  //  436   table
table.getTableHeader().setBackground(table.normalHeaderBkgd);  //  437   table
table.setRowHeight(INITIALFONTSIZE + ADDFORROWHEIGHT);  //  438   table
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);  //  439   table
  //  440   table
table.addKeyListener(new KeyAdapter() {  //  441   keystyles
@Override  //  442   keystyles
public void keyPressed(KeyEvent e) {  //  443   keystyles
int row = table.getSelectedRow();  //  444   keystyles
if (row == -1) return;  //  445   keystyles
row = table.convertRowIndexToModel(row);  //  446   keystyles
int col = table.getSelectedColumn();  //  447   keystyles
col = table.convertColumnIndexToModel(col);  //  448   keystyles
if (col != styleColIndex) return;  //  449   categories
TableModel model = table.getModel();  //  450   keystyles
String s = (String)model.getValueAt(row, styleColIndex);  //  451   keystyles
int slen = s.length();  //  452   keystyles
// s is exactly one of "" "B" "I" "BI"  //  453   keystyles
switch (e.getKeyCode()) {  //  454   keystyles
case KeyEvent.VK_B:  //  455   keystyles
if (slen > 0 && s.startsWith("B"))  //  456   keystyles
s = s.substring(1);  //  457   keystyles
else s = "B" + s;  //  458   keystyles
break;  //  459   keystyles
case KeyEvent.VK_I:  //  460   keystyles
if (slen > 0 && s.endsWith("I"))  //  461   keystyles
s = s.substring(0, slen - 1);  //  462   keystyles
else s += "I";  //  463   keystyles
break;  //  464   keystyles
case KeyEvent.VK_BACK_SPACE:  //  465   keystyles
if (slen > 0) s = s.substring(0, slen - 1);  //  466   keystyles
break;  //  467   keystyles
case KeyEvent.VK_DELETE:  //  468   keystyles
if (slen > 0) s = s.substring(1);  //  469   keystyles
break;  //  470   keystyles
default:  //  471   keystyles
if (Character.isLetterOrDigit(e.getKeyCode()))  //  472   keystyles
e.consume(); // ignore letters/digits in styles col  //  473   keystyles
return;  //  474   keystyles
}  //  475   keystyles
// now s has the new value  //  476   keystyles
model.setValueAt(s, row, styleColIndex);  //  477   keystyles
e.consume();  //  478   keystyles
}  //  479   keystyles
});  //  480   keystyles
  //  481   simplesort
// enable default sorting the table by column data  //  482   simplesort
table.setAutoCreateRowSorter(true);  //  483   simplesort
  //  484   sort
// enable sorting the table by column data  //  485   sort
table.setRowSorter(table.new FTRowSorter(table.tableData));  //  486   sort
Comparator<String> stringComparator =  //  487   sort
(s0,s1)->s0.length() == 0  //  488   sort
? (s1.length() == 0 ? 0 : +1) // non-blanks first  //  489   sort
: (s1.length() == 0 ? -1 : s0.compareToIgnoreCase(s1));  //  490   sort
Comparator<Boolean> booleanComparator =  //  491   checkboxes
(o1,o2)->o2.compareTo(o1);  //  492   checkboxes
Comparator<FontsTable.SampleFont> fontComparator =  //  493   sortbywidth
(f0,f1)->f0.getWidth()-f1.getWidth();  //  494   sortbywidth
// – – – – createFontsTable() : configure the TableModel with five columns
// Configure the columns : data, widths, renderers, and editors  //  496   table
// by setting limits on the widths of the first three columns,  //  497   table
// all excess space is allocated to the sample text column  //  498   table
  //  499   table
// we will create our own TableColumns  //  500   table
table.setAutoCreateColumnsFromModel(false);  //  501   table
// tell this JTable about the model  //  502   table
table.setModel(table.tableData);  //  503   table
  //  504   table
// STYLECOL - B for bold and I for italic  //  505   styles
styleColIndex = table.appendColumn("BI", table.styleColumn,  //  506   styles
32, 32, 32,  //  507   styles
"B-old or I-talic; click header to sort, "  //  508   coltooltips
+ "with non-blanks at the top",  //  509   coltooltips
stringComparator,  //  510   sort
null, null);  //  511   styles
table.new FTStringRenderer(STYLES_FONT), null);  //  512   cellrender
// CATCOL - the categories column  //  513   categories
TableCellEditor catEditor = table.new FTStringEditor();  //  514   celled
catColIndex = table.appendColumn("Category", table.categoryColumn,  //  515   categories
25, 75, 150,  //  516   categories
"Font category or user notes: Click header to sort",  //  517   coltooltips
stringComparator,  //  518   sort
null,  //  519   categories
table.new FTStringRenderer(LABEL_FONT),  //  520   cellrender
null);  //  521   categories
catEditor);  //  522   celled
  //  523   categories
// FONTCOL - column of font names  //  524   fonts
fontColIndex = table.appendColumn("Font Name", table.FontColumn,  //  525   fonts
75, 200, 300,  //  526   fonts
"Sort by name. Click a name to copy it to the cut buffer.",  //  527   coltooltips
stringComparator,  //  528   sort
null, null);  //  529   fonts
table.new FTStringRenderer(LABEL_FONT), null);  //  530   cellrender
  //  531   fonts
// CHECKCOL - a checkbox to select fonts  //  532   checkboxes
chkColIndex = table.appendColumn("Ck", table.checkColumn,  //  533   checkboxes
26, 26, 26,  //  534   checkboxes
"Move checked lines to the top",  //  535   coltooltips
booleanComparator,  //  536   sort
table.getDefaultRenderer(Boolean.class),  //  537   checkboxes
table.getDefaultEditor(Boolean.class));  //  538   checkboxes
  //  539   checkboxes
// SAMPLECOL - sample text in this row's font  //  540   seetext
sampleColIndex = table.appendColumn("Sample written in named font",  //  541   seetext
table.sampleColumn, 75, 300, 10000,  //  542   seetext
"Sample in the line's font; "  //  543   coltooltips
+ "click header to sort by width on screen",  //  544   coltooltips
null,  //  545   sort
fontComparator,  //  546   sortbywidth
table.new FTFontRenderer(), null);  //  547   seetext
  //  548   seetext
// set a single tooltip for all headers  //  549   table
table.getTableHeader().setToolTipText("<html>"  //  550   table
+ "To compare fonts, click under Ck & sort.<br> "  //  551   table
+ "To sort on any column, click its header.</html>");  //  552   table
// set a renderer for column headings  //  553   bettersort
table.getTableHeader()  //  554   bettersort
.setDefaultRenderer(table.new FTHeaderRenderer());  //  555   bettersort
// – – – – ---------- end createFontsTable() ----------
// after tableData initialized, start listening for changes  //  557   catschanged
table.tableData.addTableModelListener(table.modelListener);  //  558   catschanged
return table;  //  559   table
}  //  560   table
// – – – ---------- end FontViewer createFontsTable() ----------
// – – – FontsTable class : the central JTable for the application
/**  //  563   table
* The table of font names and sample strings.  //  564   table
*/  //  565   table
protected class FontsTable extends JTable {  //  566   table
DefaultTableModel tableData = new DefaultTableModel() {  //  567   table
@Override  //  568   table
// STYLECOL, CATCOL, and CHECKCOL can be edited  //  569   table
public boolean isCellEditable(int row, int col) {  //  570   table
return false  //  571   table
|| col == catColIndex  //  572   categories
|| col == chkColIndex  //  573   checkboxes
;  //  574   table
}  //  575   table
};  //  576   table
  //  577   table
javax.swing.event.TableModelListener modelListener = e-> {  //  578   catschanged
if ((e.getColumn() == catColIndex  //  579   catschanged
|| e.getColumn() == styleColIndex  //  580   styles
) && e.getType()  //  581   catschanged
== javax.swing.event.TableModelEvent.UPDATE) {  //  582   catschanged
int row = e.getFirstRow();  //  583   catschanged
String key = FONT_LIST[row].getFontName();  //  584   catschanged
TableModel mod = (TableModel)e.getSource();  //  585   catschanged
String style = (String) mod.getValueAt(row, styleColIndex);  //  586   styles
String cat = (String) mod.getValueAt(row, catColIndex);  //  587   catschanged
catsMap.put(key, cat);  //  588   catschanged
catsMap.put(key, style+";"+cat);  //  589   styles
}  //  590   catschanged
};  //  591   catschanged
  //  592   catschanged
// values to create the columns  //  593   table
String[] styleColumn; // STYLECOL  //  594   styles
String[] categoryColumn; // CATCOL  //  595   categories
final String[] FontColumn; // FONTCOL  //  596   fonts
Boolean[] checkColumn; // CHECKCOL  //  597   checkboxes
SampleFont[] sampleColumn; // SAMPLECOL  //  598   seetext
  //  599   table
// colors for header background  //  600   table
Color normalHeaderBkgd = new Color(255, 255, 0xCE);  //  601   table
Color sortingHeaderBkgd = new Color(255, 0x99, 0x33);  //  602   bettersort
  //  603   table
// backdoor value for header cell renderer  //  604   bettersort
int nowSorting = -1; // what column (in Model) is being sorted  //  605   bettersort
  //  606   bettersort
// – – – – FontsTable() constructor : build the screen contents of the table
/**  //  608   table
* Construct an uninitialized Fonts table.  //  609   table
* Call createFontable instead of this.  //  610   table
*/  //  611   table
private FontsTable() {  //  612   table
// values to create the columns  //  613   table
styleColumn = new String[FONT_LIST.length]; // STYLECOL  //  614   styles
categoryColumn = new String[FONT_LIST.length]; // CATCOL  //  615   categories
FontColumn = new String[FONT_LIST.length]; // FONTCOL  //  616   fonts
checkColumn = new Boolean[FONT_LIST.length]; // CHECKCOL  //  617   checkboxes
sampleColumn = new SampleFont[FONT_LIST.length]; // SAMPLECOL  //  618   seetext
  //  619   table
for (int inx = 0; inx < FONT_LIST.length; inx++) {  //  620   table
String fontname = FONT_LIST[inx].getFontName();  //  621   table
int hasBold = (fontname.contains("Bold")  //  622   styles
|| fontname.contains("bold")) ? 1 : 0;  //  623   styles
int hasItal = (fontname.contains("Italic")  //  624   styles
|| fontname.contains("italic")  //  625   styles
|| fontname.contains("blique")) ? 2 : 0;  //  626   styles
styleColumn[inx] = new String[]{"", "B", "I", "BI"}  //  627   styles
[hasBold+hasItal]; // STYLECOL  //  628   styles
categoryColumn[inx] = ""; // CATCOL  //  629   categories
FontColumn[inx] = fontname; // FONTCOL  //  630   fonts
checkColumn[inx] = false; // CHECKCOL  //  631   checkboxes
sampleColumn[inx] // SAMPLECOL  //  632   seetext
= new SampleFont(FONT_LIST[inx]);  //  633   seetext
}  //  634   table
  //  635   table
} // end FontsTable constructor  //  636   table
// – – – – ---------- end FontsTable() constructor ----------
// – – – – FontsTable methods
/////////////////////////////////////////////  //  639   table
// FontsTable methods  //  640   table
// – – – – – FontTable methods : appendColumn and createDefaultTableHeader
/**  //  642   table
* Append a column to the TableModel and the TableColumnModel.  //  643   table
* Convenience method for building the table.  //  644   table
*/  //  645   table
final int appendColumn(String columnName, Object[] data, int minW,  //  646   table
int prefW, int maxW,  //  647   table
String colTooltip,  //  648   coltooltips
Comparator comp,  //  649   sort
TableCellRenderer render, TableCellEditor edit) {  //  650   table
int index = tableData.getColumnCount();  //  651   table
tableData.addColumn(columnName, data);  //  652   table
TableColumn col = new TableColumn(index, prefW, render, edit);  //  653   table
col.setMinWidth(minW);  //  654   table
col.setMaxWidth(maxW);  //  655   table
columnToolTips[index] = colTooltip;  //  656   coltooltips
((FTRowSorter)getRowSorter()).setComparator(index, comp);  //  657   sort
((FTRowSorter)getRowSorter()).setSortable(index, comp!=null);  //  658   sort
addColumn(col);  //  659   table
return index;  //  660   table
}  //  661   table
  //  662   table
/** Implement table header tooltips.  //  663   coltooltips
https://docs.oracle.com/javase/tutorial/uiswing/  //  664   coltooltips
components/table.html#headertooltip  //  665   coltooltips
* @return a JTableHeader that overrides getToolTipText  //  666   coltooltips
*/  //  667   coltooltips
@Override  //  668   coltooltips
protected JTableHeader createDefaultTableHeader() {  //  669   coltooltips
return new JTableHeader(columnModel) {  //  670   coltooltips
@Override  //  671   coltooltips
public String getToolTipText(MouseEvent e) {  //  672   coltooltips
java.awt.Point p = e.getPoint();  //  673   coltooltips
int index = columnModel.getColumnIndexAtX(p.x);  //  674   coltooltips
int realIndex =  //  675   coltooltips
columnModel.getColumn(index).getModelIndex();  //  676   coltooltips
return columnToolTips[realIndex];  //  677   coltooltips
}  //  678   coltooltips
};  //  679   coltooltips
}  //  680   coltooltips
// – – – – – ---------- end FontTable methods ----------
// – – – – – FontsTable replaceCategories() : replace the categories column from a StringMap
/**  //  683   catsload
* Replace the categories data from the new sample map.  //  684   catsload
* Handles notification of the change.  //  685   catsload
* Does nothing about former values.  //  686   catsload
* @param categories The new data  //  687   catsload
*/  //  688   catsload
private void replaceCategories() {  //  689   catsload
tableData.removeTableModelListener(modelListener);  //  690   catschanged
// replace contents of each row in categories column  //  691   catsload
// by lookup of font name for that row  //  692   catsload
for (int inx = 0; inx < FONT_LIST.length; inx++) {  //  693   catsload
String fontname = FONT_LIST[inx].getFontName();  //  694   catsload
String catStr = catsMap.get(fontname);  //  695   catsload
if (catStr == null)  //  696   catsload
catStr = "";  //  697   catsload
else {  //  698   catsload
// for FontViewer, a StringMap value is style;category  //  699   catsload
// where style is one of "", "B", "I" or "BI"  //  700   catsload
// extract style code from front of catStr  //  701   catsload
int semiloc = catStr.indexOf(";");  //  702   catsload
if (semiloc >= 0) {  //  703   catsload
String styleStr = catStr.substring(0, semiloc).trim();  //  704   styles
mainTable.tableData  //  705   styles
.setValueAt(styleStr, inx, styleColIndex);  //  706   styles
catStr = catStr.substring(semiloc + 1).trim();  //  707   catsload
}  //  708   catsload
}  //  709   catsload
mainTable.tableData.setValueAt(catStr, inx, catColIndex);  //  710   catsload
}  //  711   catsload
tableData.addTableModelListener(modelListener);  //  712   catschanged
tableData.fireTableDataChanged();  //  713   catschanged
}  //  714   catsload
// – – – – – ---------- end FontsTable replaceCategories() ----------
// – – – – – FontsTable refontTheSamples() : when the size or style changes, revise the fonts for all samples
/**  //  717   seetext
* When the size or style changes, revise the fonts for all samples.  //  718   seetext
*/  //  719   seetext
private void refontTheSamples() {  //  720   seetext
// get current status  //  721   seetext
if (sampleColIndex < 0) return;  //  722   seetext
int selectedRow = getSelectedRow();  //  723   seetext
int selectedCol = getSelectedColumn();  //  724   cellfocus
int oldRowHeight = getRowHeight();  //  725   rowheight
// record viewRect before it is overwritten by changeSelection()  //  726   rowvis
JViewport vp = scrollTable.getViewport();  //  727   rowvis
Rectangle viewRect = vp.getViewRect(); // the visible rectangle  //  728   rowvis
// fetch the parameters for new fonts  //  729   seetext
int fontsize = INITIALFONTSIZE;  //  730   seetext
int fontsize = sizeModel.getNumber().intValue();  //  731   picksize
int styleInt = Font.PLAIN;  //  732   seetext
int styleInt = STYLE_VALUES[styles.getSelectedIndex()];  //  733   pickstyle
  //  734   seetext
// replace the fonts in all samples  //  735   seetext
for (int inx = 0; inx < FONT_LIST.length; inx++) {  //  736   seetext
Font newf = new SampleFont(FONT_LIST[inx]  //  737   seetext
.deriveFont(styleInt, (float)fontsize));  //  738   seetext
tableData.setValueAt(newf, inx, sampleColIndex);  //  739   seetext
}  //  740   seetext
  //  741   seetext
// choose a table row height appropriate to fontsize  //  742   rowheight
int newRowHeight = Math.max(INITIALFONTSIZE - 4, fontsize)  //  743   rowheight
+ ADDFORROWHEIGHT;  //  744   rowheight
  //  745   rowheight
// if selected cell was visible,  //  746   rowvis
// scroll so it shows close to where it was  //  747   rowvis
// but if it was offscreen,  //  748   rowvis
// scroll to keep the top line where it was  //  749   rowvis
int toprow;  //  750   rowvis
if (selectedRow == -1)  //  751   rowvis
// no selection, retain existing top row  //  752   rowvis
toprow = rowAtPoint(new Point(0, viewRect.y));  //  753   rowvis
else {  //  754   rowvis
// return the focus to the selected cell, if any  //  755   cellfocus
// (JTable turns on autoscrolls, but it scrolls twice:  //  756   cellfocus
// here and in setViewPosition() below)  //  757   cellfocus
setAutoscrolls(false); // momentarily block autoscrolls  //  758   cellfocus
changeSelection(selectedRow, selectedCol, false, false);  //  759   cellfocus
setAutoscrolls(true); // re-enable autoscrolls  //  760   cellfocus
  //  761   cellfocus
Rectangle s // the location of the selectedRow  //  762   rowvis
= getCellRect(selectedRow, 0, true);  //  763   rowvis
if (s.y + s.height - SLIVER < viewRect.y  //  764   rowvis
|| s.y > viewRect.y + viewRect.height - SLIVER)  //  765   rowvis
// the selection was not in the view, retain existing top row  //  766   rowvis
toprow = rowAtPoint(new Point(0, viewRect.y));  //  767   rowvis
else {  //  768   rowvis
// scroll selectedRow to near its old position on the screen  //  769   rowvis
int offset = s.y - viewRect.y;  //  770   rowvis
toprow = selectedRow  //  771   rowvis
- (offset-SLIVER) / (INITIALFONTSIZE+ADDFORROWHEIGHT);  //  772   rowvis
toprow = selectedRow - (offset - SLIVER) / newRowHeight;  //  773   rowheight
}  //  774   rowvis
}  //  775   rowvis
setRowHeight(newRowHeight); // change view geometry !  //  776   rowheight
int yTop = getCellRect(toprow, 0, true).y;  //  777   rowvis
vp.setViewPosition(new Point(0, yTop));  //  778   rowvis
  //  779   rowvis
// the focus has moved to the sizes or styles widget  //  780   cellfocus
// return the focus to the table  //  781   cellfocus
requestFocusInWindow();  //  782   cellfocus
  //  783   cellfocus
repaint(); // re-render all (to get new samples displayed)  //  784   seetext
} // end refontTheSamples  //  785   seetext
// – – – – – ---------- end FontsTable refontTheSamples() ----------
// – – – – – FontsTable reviseSamples() : after the edittext has changed in the SampleText, revise all samples
/** sample text has changed, trigger a repaint of all sample cells. */  //  788   seetext
private void reviseSamples() {  //  789   seetext
for (int inx = 0; inx < FONT_LIST.length; inx++) // cause repaint  //  790   seetext
tableData.fireTableCellUpdated(inx, sampleColIndex);  //  791   seetext
}  //  792   seetext
// – – – – – ---------- end FontsTable reviseSamples() ----------
// – – – – – FontsTable finishEditing() : called when any open cell editor should be closed
/** Close any open cell editor */  //  795   finishedit
public void finishEditing() {  //  796   finishedit
CellEditor ed = getCellEditor();  //  797   finishedit
if (ed != null)  //  798   finishedit
ed.stopCellEditing();  //  799   finishedit
}  //  800   finishedit
// – – – – – ---------- end FontsTable finishEditing() ----------
// – – – – ---------- end FontsTable methods ----------
// – – – – FontsTable : support classes
//////////////////////////////////////////  //  804   table
// Classes for FontsTable  //  805   table
  //  806   table
// – – – – – FontsTable SampleFont : a Font subclass with the right toString() value
/**  //  808   seetext
* The samples column is an array of Font objects,  //  809   seetext
* If the user types ctl-C to copy a samples column cell,  //  810   seetext
* the value later pasted will be from Font.toString(). Not useful.  //  811   seetext
*  //  812   seetext
* SampleFont.toString() returns the String that actually appears  //  813   seetext
*  //  814   seetext
*/  //  815   seetext
class SampleFont extends Font {  //  816   seetext
int width = -1;  //  817   sortbywidth
java.awt.font.FontRenderContext renderContext  //  818   sortbywidth
= new java.awt.image.BufferedImage(1, 1,  //  819   sortbywidth
java.awt.image.BufferedImage.TYPE_4BYTE_ABGR)  //  820   sortbywidth
.getGraphics().getFontMetrics()  //  821   sortbywidth
.getFontRenderContext();  //  822   sortbywidth
/**  //  823   seetext
* Constructor from a Font. The default consructor cannot be used  //  824   seetext
* because Font does not have a constructor with no arguments.  //  825   seetext
* @param f The Font for this sample.  //  826   seetext
*/  //  827   seetext
public SampleFont(Font f) {  //  828   seetext
super(f);  //  829   seetext
}  //  830   seetext
/** @return the sample string */  //  831   seetext
@Override public String toString() {  //  832   seetext
return sampleEditor.getText();  //  833   seetext
}  //  834   seetext
/**  //  835   sortbywidth
* @return Screen width of sample text in this font.  //  836   sortbywidth
* For sorting the rows by sample width  //  837   sortbywidth
*/  //  838   sortbywidth
public int getWidth() {  //  839   sortbywidth
return (width >= 0) ? width  //  840   sortbywidth
: (width = (int)getStringBounds(  //  841   sortbywidth
sampleEditor.getText(), renderContext)  //  842   sortbywidth
.getWidth());  //  843   sortbywidth
}  //  844   sortbywidth
}  //  845   seetext
// – – – – – ---------- end FontsTable SampleFont ----------
// – – – – – FontsTable FTStringRenderer : a Cell renderer for categories and fontnames
/** Render a table cell with my chosen font and border. */  //  848   cellrender
class FTStringRenderer extends DefaultTableCellRenderer {  //  849   cellrender
Font columnFont;  //  850   cellrender
/**  //  851   cellrender
* Constructor to specify a font for the column contents.  //  852   cellrender
* @param f the font, STYLES_FONT or LABEL_FONT  //  853   cellrender
*/  //  854   cellrender
FTStringRenderer(Font f) {  //  855   cellrender
super();  //  856   cellrender
columnFont = f;  //  857   cellrender
}  //  858   cellrender
/** Tailor this CellRenderer as appropriate. */  //  859   cellrender
@Override  //  860   cellrender
public Component getTableCellRendererComponent(  //  861   cellrender
JTable table,  //  862   cellrender
Object value, // the Font for this sample cell  //  863   cellrender
boolean isSelected, boolean hasFocus,  //  864   cellrender
int row, int column) {  //  865   cellrender
if (hasFocus && column == fontColIndex)  //  866   fillcut
fillCutBuffer((String)value);  //  867   fillcut
setText((String)value);  //  868   cellrender
setFont(columnFont);  //  869   cellrender
setBorder(hasFocus ? BORDER_BLUE : CELL_BORDER);  //  870   cellrender
setForeground(hasFocus ? Color.BLUE : Color.BLACK);  //  871   cellrender
return this;  //  872   cellrender
}  //  873   cellrender
}  //  874   cellrender
  //  875   cellrender
// – – – – – ---------- end FontsTable FTStringRenderer ----------
// – – – – – FontsTable FTStringEditor : an editor for styles and categories
/** Edit a table cell with my chosen font and border. */  //  878   celled
class FTStringEditor extends AbstractCellEditor  //  879   celled
implements TableCellEditor {  //  880   celled
/**  //  881   edpresel
* An implementation of DefaultCaret that can skip a setDot.  //  882   edpresel
* This makes it possible to select the entire contents of a cell  //  883   edpresel
* when first opening it for editing.  //  884   edpresel
*/  //  885   edpresel
class SkippyCaret extends javax.swing.text.DefaultCaret {  //  886   edpresel
boolean skipOne = false;  //  887   edpresel
@Override  //  888   edpresel
public void setDot(int dot,  //  889   edpresel
javax.swing.text.Position.Bias dotBias) {  //  890   edpresel
if (skipOne) skipOne = false;  //  891   edpresel
else super.setDot(dot, dotBias);  //  892   edpresel
}  //  893   edpresel
};  //  894   edpresel
final SkippyCaret localCaret = new SkippyCaret();  //  895   edpresel
final JTextField component = new JTextField() {{  //  896   celled
setForeground(Color.RED);  //  897   celled
setFont(LABEL_FONT);  //  898   celled
setCaret(localCaret);  //  899   edpresel
}};  //  900   celled
@Override  //  901   celled
public Component getTableCellEditorComponent(  //  902   celled
JTable table,  //  903   celled
Object value,  //  904   celled
boolean isSelected,  //  905   celled
int row,  //  906   celled
int column) {  //  907   celled
component.setText((String) value);  //  908   celled
localCaret.setDot(0);  //  909   edpresel
localCaret.moveDot(((String)value).length());  //  910   edpresel
localCaret.skipOne = true;  //  911   edpresel
return component;  //  912   celled
}  //  913   celled
@Override  //  914   celled
public Object getCellEditorValue() {  //  915   celled
return component.getText();  //  916   celled
}  //  917   celled
}  //  918   celled
// – – – – – ---------- end FontsTable FTStringEditor ----------
// – – – – – FontsTable FTFontRenderer : render the sample text; the data is actually the Font object
/** render the samples column;  //  921   seetext
* show the edittext in the row's named Font. */  //  922   seetext
class FTFontRenderer extends DefaultTableCellRenderer {  //  923   seetext
@Override  //  924   seetext
public Component getTableCellRendererComponent(  //  925   seetext
JTable table,  //  926   seetext
Object value, // the Font for this sample cell  //  927   seetext
boolean isSelected, boolean hasFocus,  //  928   seetext
int row, int column) { // these two parameters are ignored  //  929   seetext
JLabel renderer = this; //DefaultTableCellRenderer extends JLabel  //  930   seetext
renderer.setText(sampleEditor.getText());  //  931   seetext
renderer.setFont((Font)value);  //  932   seetext
renderer.setBorder(hasFocus ? BORDER_BLUE : CELL_BORDER);  //  933   seetext
renderer.setForeground(hasFocus ? Color.BLUE : Color.BLACK);  //  934   seetext
return renderer;  //  935   seetext
}  //  936   seetext
}  //  937   seetext
// – – – – – ---------- end FontsTable FTFontRenderer ----------
// – – – – – FontsTable FTHeaderRenderer : render the column headers individually
/** Render column headings.  //  940   bettersort
* Omit the "sorting arrow"; we always sort ascending  //  941   bettersort
* Color the header background during the sort  //  942   bettersort
*/  //  943   bettersort
class FTHeaderRenderer extends DefaultTableCellRenderer {  //  944   bettersort
@Override  //  945   bettersort
public Component getTableCellRendererComponent(  //  946   bettersort
JTable table, Object value, boolean isSelected,  //  947   bettersort
boolean hasFocus, int row, int column) {  //  948   bettersort
int modelCol = convertColumnIndexToModel(column);  //  949   bettersort
setText(" " + (String)value);  //  950   bettersort
setOpaque(true);  //  951   bettersort
setFont(HEADER_FONT);  //  952   bettersort
setBackground(nowSorting == modelCol  //  953   bettersort
? sortingHeaderBkgd : normalHeaderBkgd);  //  954   bettersort
setForeground(Color.BLUE);  //  955   bettersort
if (modelCol == styleColIndex)  //  956   styles
setHorizontalAlignment(JLabel.CENTER);  //  957   styles
return this;  //  958   bettersort
}  //  959   bettersort
/** the header looks better with a bottom line */  //  960   bettersort
@Override  //  961   bettersort
protected void paintBorder(Graphics g) {  //  962   bettersort
g.drawLine(0, getHeight() - 1, getWidth(), getHeight() - 1);  //  963   bettersort
}  //  964   bettersort
}  //  965   bettersort
// – – – – – ---------- end FontsTable FTHeaderRenderer ----------
// – – – – – FontsTable FTRowSorter : sort rows
/**  //  968   sort
* Specialize sorting to patch over DefaultRowSorter bugs  //  969   sort
* and to replace the usual sort behaviors.  //  970   sort
*/  //  971   sort
private class FTRowSorter  //  972   sort
extends TableRowSorter<DefaultTableModel> {  //  973   sort
// keep our own list of comparators  //  974   sort
ArrayList<Comparator> comparators = new ArrayList<>();  //  975   sort
  //  976   sort
/**  //  977   sort
* Construct a row sorter.  //  978   sort
* @param tmodel The TableModel being sorted (mainTable)  //  979   sort
*/  //  980   sort
FTRowSorter(DefaultTableModel tmodel) {  //  981   sort
super(tmodel);  //  982   sort
}  //  983   sort
/** Get the comparator from our internal array */  //  984   sort
@Override  //  985   sort
public Comparator<?> getComparator(int col) {  //  986   sort
return comparators.get(col);  //  987   sort
}  //  988   sort
/**  //  989   sort
* Remember a comparator.  //  990   sort
* JAVA LIBRARY BUG: DefaultRowSorter (behavior is documented)  //  991   sort
* discards prior comparators when a column is added  //  992   sort
* @param index which comparator to set  //  993   sort
* @param comp the new comparator  //  994   sort
*/  //  995   sort
@Override  //  996   sort
public void setComparator(int index, Comparator<?>comp) {  //  997   sort
while (index > comparators.size() - 1)  //  998   sort
comparators.add(null); // (in practice, adds one element)  //  999   sort
comparators.set(index, comp);  //  1000   sort
}  //  1001   sort
/** Never use toString for table row sorting.  //  1002   sort
* JAVA LIBRARY BUG: by not calling super.setComparator,  //  1003   sort
* DefaultRowSorter decides to use toString  //  1004   sort
* @param column Which column to reply for  //  1005   sort
* @return false  //  1006   sort
*/  //  1007   sort
@Override  //  1008   sort
protected boolean useToString(int column) { return false; }  //  1009   sort
/**  //  1010   sort
* Do a sort for a given set of keys. In this application  //  1011   sort
* each sort has only one key.  //  1012   sort
* <pre>  //  1013   bettersort
* Normal sort has two problems:  //  1014   bettersort
* a) if the table has been sorted on a column,  //  1015   bettersort
* reclicking the header does not sort again  //  1016   bettersort
* b) successive sorts are in opposite directions;  //  1017   bettersort
* this is not useful for fonts, so we sort always ascending  //  1018   bettersort
* This override of setSortKeys fixes both problems  //  1019   bettersort
* </pre>  //  1020   bettersort
* @param keys the list of keys to sort on.  //  1021   bettersort
*/  //  1022   sort
@Override  //  1023   sort
public void setSortKeys(  //  1024   sort
java.util.List<? extends SortKey> keys) {  //  1025   sort
finishEditing(); // save any in-progress category edit  //  1026   finishedit
super.setSortKeys(keys);  //  1027   sort
java.util.List<SortKey> copy  //  1028   bettersort
= new ArrayList<>(keys.size());  //  1029   bettersort
for (SortKey k : keys)  //  1030   bettersort
copy.add(new SortKey(k.getColumn(),  //  1031   bettersort
SortOrder.ASCENDING));  //  1032   bettersort
// during the sort, the column header changes color  //  1033   bettersort
// the column number in the sortkey is per the model  //  1034   bettersort
nowSorting = copy.get(0).getColumn();  //  1035   bettersort
final JTableHeader tableHdr = FontsTable.this.getTableHeader();  //  1036   bettersort
tableHdr.paintImmediately(0, 0, // change hdr color NOW  //  1037   bettersort
Integer.MAX_VALUE, Integer.MAX_VALUE);  //  1038   bettersort
  //  1039   bettersort
java.util.List<SortKey> newSortKeys  //  1040   bettersort
= Collections.unmodifiableList(copy);  //  1041   bettersort
if (getSortKeys().equals(newSortKeys))  //  1042   bettersort
allRowsChanged(); // sort, the same way as before  //  1043   bettersort
else  //  1044   bettersort
super.setSortKeys(copy); // sort the new way  //  1045   bettersort
  //  1046   bettersort
SwingUtilities.invokeLater(()->{  //  1047   bettersort
nowSorting = -1; // turn off header color  //  1048   bettersort
scrollTable.getVerticalScrollBar().setValue(0); // scroll to top  //  1049   scroll
tableHdr.paintImmediately(0, 0, // revert header color  //  1050   bettersort
Integer.MAX_VALUE, Integer.MAX_VALUE);  //  1051   bettersort
});  //  1052   bettersort
} // end setSortKeys  //  1053   sort
} // end FTPRowSorter  //  1054   sort
// – – – – – ---------- end FontsTable FTRowSorter ----------
// – – – – ---------- end FontsTable ----------
} // end FontsTable  //  1057   table
// – – – ---------- end FontsTable class ----------
// – – ---------- end FontViewer enclosed classes ----------
// – – FontViewer - Menu : Creating menu items, saving categories, user interface
// – – – FontViewer createAction() : define actions for menus and keystrokes
/**  //  1062   action
* Create an Action object for insertion in a menu or keymap.  //  1063   action
* Sets the properties NAME, DISPLAYED_MNEMONIC_INDEX_KEY,  //  1064   action
* SHORT_DESCRIPTION, ACCELERATOR_KEY. Obviates MNEMONIC_KEY.  //  1065   action
*  //  1066   action
* @param main JComponent whose action and key maps should be  //  1067   action
* revised for accelerator keys  //  1068   action
* @param name Name that should appear in a menu or button.  //  1069   action
* If the name contains an underscore (_), the following character  //  1070   action
* is identified as the mnemonic (usually underlined). The underline  //  1071   action
* itself is deleted.  //  1072   action
* @param tooltip  //  1073   action
* If not null, the string is displayed as the tooltip for the menu item.  //  1074   action
* @param accelerator If non-null, must be a String in the format  //  1075   action
* defined by KeyStroke.getKeyStroke.  //  1076   action
* Examples "ctrl S" (must be upper-case)  //  1077   action
<pre> "alt typed A" "meta ctrl DELETE" </pre>  //  1078   action
* @param tobedone An Actor typically created with a lambda expression:  //  1079   action
<pre> Action twiddle = createAction("Do nothing",  //  1080   action
* "waste computer cycles",  //  1081   action
* null, () -&gt; { twiddle(thumbs); }); </pre>  //  1082   action
* @return An Action suitable for a button or menuitem.  //  1083   action
* If additional properties are needed,  //  1084   action
* assign the value to a temporary and add properties to it.  //  1085   action
*/  //  1086   action
public static Action createAction(JComponent main, String name,  //  1087   action
String tooltip, String accelerator,  //  1088   action
ActionListener tobedone) {  //  1089   action
AbstractAction act = new AbstractAction() {  //  1090   action
@Override  //  1091   action
public void actionPerformed(ActionEvent e) {  //  1092   action
tobedone.actionPerformed(e);  //  1093   action
}  //  1094   action
};  //  1095   action
int undX = name.indexOf('_');  //  1096   action
if (undX >= 0) {  //  1097   action
name = name.replace("_","");  //  1098   action
act.putValue(Action.MNEMONIC_KEY,  //  1099   action
KeyEvent.getExtendedKeyCodeForChar(name.charAt(undX)));  //  1100   action
if (undX > 0)  //  1101   action
act.putValue(Action.DISPLAYED_MNEMONIC_INDEX_KEY, undX);  //  1102   action
}  //  1103   action
act.putValue(Action.NAME, name);  //  1104   action
if (tooltip != null)  //  1105   action
act.putValue(Action.SHORT_DESCRIPTION, tooltip);  //  1106   action
if (accelerator != null) {  //  1107   action
KeyStroke ks = KeyStroke.getKeyStroke(accelerator);  //  1108   action
act.putValue(Action.ACCELERATOR_KEY, ks);  //  1109   action
}  //  1110   action
return act;  //  1111   action
}  //  1112   action
  //  1113   action
/**  //  1114   ccpaction
* Create a menu item based on a given cut/copy/paste Action  //  1115   ccpaction
* from DefaultEditorKit.  //  1116   ccpaction
* @param act The Action to be initiated by this menu item.  //  1117   ccpaction
* @param name The String to appear in the menu.  //  1118   ccpaction
* @param mnemX Index of letter to be underlined in name on menu.  //  1119   ccpaction
* @param acc The accelerator key.  //  1120   ccpaction
* Displayed on menu and set in key bindings.  //  1121   ccpaction
*/  //  1122   ccpaction
static JMenuItem ccpMenuItem(Action act,  //  1123   ccpaction
String name, int mnemX, String acc) {  //  1124   ccpaction
JMenuItem item = new JMenuItem(act);  //  1125   ccpaction
item.setText(name);  //  1126   ccpaction
char ch = name.charAt(mnemX);  //  1127   ccpaction
item.setMnemonic(KeyEvent.getExtendedKeyCodeForChar(ch));  //  1128   ccpaction
if (mnemX > 0)  //  1129   ccpaction
item.setDisplayedMnemonicIndex(mnemX);  //  1130   ccpaction
item.setAccelerator(KeyStroke.getKeyStroke(acc));  //  1131   ccpaction
return item;  //  1132   ccpaction
}  //  1133   ccpaction
// – – – ---------- end FontViewer createAction() ----------
// – – – FontViewer insertMenuItems() : define menus
/**  //  1136   menu
* Add JMenus to a MenuBar and add FonViewer-specific Actions to each.  //  1137   menu
* Conceivably a client could add other items to the menubar.  //  1138   menu
* @param bar a JMenuBar to be filled in  //  1139   menu
*/  //  1140   menu
public void insertMenuItems(JMenuBar bar) {  //  1141   menu
// FILE menu  //  1142   menu
JMenu fileMenu = new JMenu("File" );  //  1143   menu
fileMenu.setMnemonic(KeyEvent.VK_F);  //  1144   menu
bar.add(fileMenu);  //  1145   menu
// FILE->LOAD CATEGORIES  //  1146   menu
fileMenu.add(createAction(FontViewer.this,  //  1147   catsreadui
"_Load categories ...",  //  1148   catsreadui
"Prompt for the location of an "  //  1149   catsreadui
+ "additional categories file to load ",null,  //  1150   catsreadui
e->askForCatSource()  //  1151   catsreadui
));  //  1152   catsreadui
// FILE->SAVE CATEGORIES  //  1153   catssave
fileMenu.add(saveAction=createAction(FontViewer.this,  //  1154   catssave
"_Save categories",  //  1155   catssave
(IS_IN_WEBSTART ? "Saving is disallowed under WebStart"  //  1156   catssave
: "Save the category data (\"About\" on the Help menu "  //  1157   catssave
+ "shows where it will be saved)"),  //  1158   catssave
"control pressed S",  //  1159   catssave
e->{  //  1160   catssave
mainTable.finishEditing();  //  1161   finishedit
catsMap.save();  //  1162   catssave
}));  //  1163   catssave
if (saveAction == null) {}  //  1164   catsreadui
else if (IS_IN_WEBSTART) saveAction.setEnabled(false);  //  1165   webstart
else saveAction.setEnabled(catsMap.getMapDest() != null);  //  1166   catsreadui
// FILE->SAVE AS  //  1167   catssaveas
fileMenu.add(saveasAction=createAction(FontViewer.this,  //  1168   catssaveas
"Save _as ...",  //  1169   catssaveas
IS_IN_WEBSTART ? "Saving is disallowed under WebStart"  //  1170   webstart
: "Prompt for a location "  //  1171   catssaveas
+ "and begin saving the category data there",  //  1172   catssaveas
null,  //  1173   catssaveas
e->{  //  1174   catssaveas
mainTable.finishEditing();  //  1175   finishedit
JButton help = new JButton("Help");  //  1176   catssaveas
help.addActionListener(butev->browseGuide("categoriesfile"));  //  1177   catssaveas
Object[] buttons = new Object[]{"Save", "Don't save", help};  //  1178   catssaveas
JTextField dest = new JTextField(DEFAULT_CATSMAP, 40);  //  1179   catssaveas
int opt = talkToUser("Save Category Data To",  //  1180   catssaveas
"Where would you like to store category data?",  //  1181   catssaveas
dest, buttons);  //  1182   catssaveas
if (opt == 0) {  //  1183   catssaveas
String errmsg = setSaveFile(dest.getText());  //  1184   catssaveas
if (errmsg != null)  //  1185   telluser
tellUser("File save failed", errmsg);  //  1186   telluser
}  //  1187   catssaveas
}));  //  1188   catssaveas
if (IS_IN_WEBSTART) {  //  1189   webstart
String msg = "You can modify the first two table columns;"  //  1190   webstart
+ "<br>but FontViewer is currently running under"  //  1191   webstart
+ "<br><b>Java WebStart</b>, "  //  1192   webstart
+ "so your changes cannot be saved.";  //  1193   webstart
tellUser("Not saving category data", msg);  //  1194   webstart
saveasAction.setEnabled(false);  //  1195   webstart
}  //  1196   webstart
// FILE->EXIT  //  1197   shut
fileMenu.add(createAction(FontViewer.this, "E_xit",  //  1198   shut
"Make sure categories are saved, and close window",  //  1199   shut
null,  //  1200   shut
e->{  //  1201   shut
final Container top = getTopLevelAncestor();  //  1202   shut
if (top instanceof Window)  //  1203   shut
// simulate clicking the red X (send a WindowClosing event)  //  1204   shut
TOOLKIT.getSystemEventQueue()  //  1205   shut
.postEvent(new WindowEvent((Window) top,  //  1206   shut
WindowEvent.WINDOW_CLOSING));  //  1207   shut
}  //  1208   shut
));  //  1209   shut
  //  1210   shut
// EDIT menu  //  1211   ccpaction
JMenu editMenu = new JMenu("Edit");  //  1212   ccpaction
editMenu.setMnemonic(KeyEvent.VK_E);  //  1213   ccpaction
bar.add(editMenu);  //  1214   ccpaction
editMenu.add(ccpMenuItem(new DefaultEditorKit.CutAction(),  //  1215   ccpaction
"Cut", 2, "control X"));  //  1216   ccpaction
editMenu.add(ccpMenuItem(new DefaultEditorKit.CopyAction(),  //  1217   ccpaction
"Copy", 0, "control C"));  //  1218   ccpaction
editMenu.add(ccpMenuItem(new DefaultEditorKit.PasteAction(),  //  1219   ccpaction
"Paste", 0, "control V"));  //  1220   ccpaction
editMenu.add(sampleEditor.resetAction);  //  1221   resettext
  //  1222   ccpaction
// HELP menu  //  1223   menu
JMenu helpMenu = new JMenu("Help");  //  1224   menu
helpMenu.setMnemonic(KeyEvent.VK_H);  //  1225   menu
bar.add(helpMenu);  //  1226   menu
// HELP->USING FONTVIEWER  //  1227   guide
helpMenu.add(createAction(FontViewer.this,  //  1228   guide
"_Using FontViewer",  //  1229   guide
"Browse the instructions for using FontViewer",  //  1230   guide
"F1",  //  1231   guide
e->browseGuide(null)  //  1232   guide
));  //  1233   guide
// HELP->ABOUT FONTVIEWER  //  1234   about
helpMenu.add(createAction(FontViewer.this,  //  1235   about
"_About FontViewer",  //  1236   about
"FontViewer version and info on categories file",  //  1237   about
null,  //  1238   about
e->about()  //  1239   about
));  //  1240   about
}  //  1241   menu
// – – – ---------- end FontViewer insertMenuItems() ----------
// – – – FontViewer loadCat() : Load categories from various places
/**  //  1244   catsload
* Read categories  //  1245   catsload
* and, if successfully read from a local file,  //  1246   catssaveas
* add the source name to the categorySources array.  //  1247   catssaveas
* @param name String naming a source for category data.  //  1248   catsload
* @return True if the read succeeded.  //  1249   catsload
*/  //  1250   catsload
private boolean loadCat(String name) {  //  1251   catsload
if ( ! catsMap.read(name))  //  1252   catsload
return false;  //  1253   catsload
categorySources.add(name);  //  1254   about
return true;  //  1255   catsload
}  //  1256   catsload
/**  //  1257   catsinit
* Load categories data from a few standard location.  //  1258   catsinit
* The ordering gets the latest data from the website or, if it isn't  //  1259   catsinit
* available, the jar installation directory. And then the user's  //  1260   catsinit
* additions are read, overriding any previous values.  //  1261   catsinit
* The locations expand to  //  1262   catsinit
* jardir:/fontcategories.txt  //  1263   catsinit
* http://physpics.com/Java/apps/fontviewer/fontcategories.txt  //  1264   catsinit
* resource:/fontcategories.txt (top-level of jar file)  //  1265   catsinit
* userhome:/FontViewer/fontcategories.txt (default save location)  //  1266   catsinit
*/  //  1267   catsinit
public void loadInitialCategories() {  //  1268   catsinit
loadCategories(new String[]{  //  1269   catsinit
"jardir:" + CATS_FILE,  //  1270   catsinit
SITE_URL + "/" + CATS_FILE,  //  1271   catsinit
"resource:/"+CATS_FILE,  //  1272   catsinit
DEFAULT_CATSMAP  //  1273   catssave
});  //  1274   catsinit
}  //  1275   catsinit
/** Load categories from a list of specified locations.  //  1276   catsload
* Set categoriesSourceFile to the latest source read. Nominally,  //  1277   catsload
* the last location read from should be the user's own categories,  //  1278   catsload
* overriding the more general categories data from other sources.  //  1279   catsload
* @param srcName a String array of categories sources.  //  1280   catsload
* @return true iff at least one source was read successfully  //  1281   catsload
*/  //  1282   catsload
public boolean loadCategories(String[] srcName) {  //  1283   catsload
int origSrcCnt = categorySources.size();  //  1284   about
for (String fnm : srcName)  //  1285   catsload
loadCat(fnm);  //  1286   catsload
if (origSrcCnt < categorySources.size()) {  //  1287   about
mainTable.replaceCategories();  //  1288   catsload
return true;  //  1289   catsload
}  //  1290   about
return false;  //  1291   about
}  //  1292   catsload
/** Prompt for a location and load categories data from it. */  //  1293   catsreadui
public void askForCatSource() {  //  1294   catsreadui
if (IS_IN_WEBSTART)  //  1295   webstart
return;  //  1296   webstart
JButton help = new JButton("Help");  //  1297   catsreadui
help.addActionListener(butev->browseGuide("categoriesfile"));  //  1298   catsreadui
Object[] buttons = new Object[]{"Load", "Cancel", help};  //  1299   catsreadui
JTextField dest = new JTextField(DEFAULT_CATSMAP, 40);  //  1300   catsreadui
int opt = talkToUser("Load Categories Datsa From",  //  1301   catsreadui
"Choose a location for reading categories data",  //  1302   catsreadui
dest, buttons);  //  1303   catsreadui
if (opt != 0) return;  //  1304   catsreadui
if (loadCat(dest.getText()))  //  1305   catsreadui
mainTable.replaceCategories();  //  1306   catsreadui
else  //  1307   telluser
tellUser("Load failed",  //  1308   telluser
"Could not read category data from<br>     "  //  1309   telluser
+ "<code>" + dest.getText() + "</code>");  //  1310   telluser
}  //  1311   catsreadui
// – – – ---------- end FontViewer loadCat() ----------
// – – – FontViewer setSaveFile() : Establish category save file
/**  //  1314   catssave
* Set the catsMap save file and do an initial save to it.  //  1315   catssave
* @param fnm File name of file to save into.  //  1316   catssave
* May be null or "" to turn off saving  //  1317   catssave
* @return null if changed the save file and saved the data;  //  1318   catssave
* otherwise return an error message.  //  1319   catssave
* If webstart, always an error.  //  1320   webstart
*/  //  1321   catssave
public String setSaveFile(String fnm) {  //  1322   catssave
if (IS_IN_WEBSTART)  //  1323   webstart
return "The program cannot save category data "  //  1324   webstart
+ "when running under webstart";  //  1325   webstart
StringMap.Dest val = catsMap.setMapDest(fnm);  //  1326   catssave
if (val != StringMap.Dest.FAILED) {  //  1327   catssave
if (saveAction != null)  //  1328   catssave
saveAction.setEnabled(catsMap.getMapDest() != null);  //  1329   catssave
catsMap.save(); // do initial save  //  1330   catssave
if (catsMap.flush()) // check that no changes are pending  //  1331   catssave
return null;  //  1332   catssave
}  //  1333   catssave
// == Dest.FAILED  //  1334   catssave
if (saveAction != null)  //  1335   catssave
saveAction.setEnabled(false);  //  1336   catssave
String msg = "FontViewer could not save category data in<br>"  //  1337   catssave
+ "<code>     "  //  1338   catssave
+ (fnm==null||fnm.isEmpty() ? "(not saving data)" : fnm)  //  1339   catssave
+ "</code><br>"  //  1340   catssave
+ "To save data, click the "  //  1341   catssaveas
+ "<b>Save as ...</b> option in the <b>File</b> menu.";  //  1342   catssaveas
return msg;  //  1343   catssave
}  //  1344   catssave
// – – – ---------- end FontViewer setSaveFile() ----------
// – – – FontViewer help options : usage instructions and the "About" text
/** Browse FVHelp.html from with the user's default browser.  //  1347   guide
* @param anchor null, or the name of an anchor in HELP_URL (no '#')  //  1348   guide
*/  //  1349   guide
public static void browseGuide(String anchor) {  //  1350   guide
String url = HELP_URL + (anchor == null ? "" : "#" + anchor);  //  1351   guide
Desktop dt;  //  1352   guide
if (Desktop.isDesktopSupported() && (dt=Desktop.getDesktop())  //  1353   guide
.isSupported(Desktop.Action.BROWSE)) {  //  1354   guide
try {  //  1355   guide
dt.browse(new java.net.URI(url));  //  1356   guide
return;  //  1357   guide
}  //  1358   guide
catch (java.io.IOException | java.net.URISyntaxException ex) {  //  1359   guide
// fall thru  //  1360   guide
}  //  1361   guide
}  //  1362   guide
String msg = "Please browse <a href='"+url+"'>"  //  1363   guide
+"FontViewer Help</a>";  //  1364   guide
String msg = "Please browse <a href='"+url+"'>"  //  1365   fillcut
+"FontViewer Help</a>" + fillCutBuffer(url);  //  1366   fillcut
JLabel lblmsg = new JLabel("<html>" + msg + "</html>");  //  1367   guide
JOptionPane.showMessageDialog(null, lblmsg);  //  1368   guide
tellUser("FontViewer Help", msg);  //  1369   telluser
}  //  1370   guide
/**  //  1371   about
* Show a message with the version of FontViewer  //  1372   about
* and which font categories files are in play.  //  1373   about
*/  //  1374   about
public void about() {  //  1375   about
// Get version string from MANIFEST.MF in the jar file  //  1376   about
String version = getClass().getPackage().getImplementationVersion();  //  1377   about
String msg = "<a href='"+SITE_URL+"/'>"  //  1378   about
+ "FontViewer</a> version " + version  //  1379   about
+ "   by ZweiBieren@PhysPics.com";  //  1380   about
msg += fillCutBuffer(SITE_URL + "/");  //  1381   fillcut
msg += "<br>Categories have been read from these sources:";  //  1382   catsload
for (String src : categorySources)  //  1383   catsload
msg += "<br>     <code>"+src+"</code>";  //  1384   catsload
if (IS_IN_WEBSTART) msg += "<br>Changes are not being saved "  //  1385   webstart
+ "because we are running via Webstart";  //  1386   webstart
else {  //  1387   webstart
File dest = catsMap.getMapDest();  //  1388   catssave
msg += (dest != null)  //  1389   catssave
? "<br>Now saving to <br>     <code>"  //  1390   catssave
+ dest.getAbsolutePath() + "</code>"  //  1391   catssave
: "<br>NOT SAVING CATEGORY DATA."  //  1392   catssave
+ "<br>You may want to choose \"Save as ...\""  //  1393   catssaveas
+ " from the File menu";  //  1394   catssaveas
}  //  1395   webstart
JLabel lblmsg = new JLabel("<html>" + msg + "</html>");  //  1396   about
JOptionPane.showMessageDialog(this, lblmsg);  //  1397   about
tellUser("About FontViewer", msg);  //  1398   telluser
}  //  1399   about
// – – – ---------- end FontViewer help options ----------
// – – – FontViewer tellUser() : Dialog boxes and the cut buffer
/** Put new content in the cut buffer.  //  1402   fillcut
* @param cutee The String to put in the cut buffer.  //  1403   fillcut
* @return If cutbuffer is set, return a message about it.  //  1404   fillcut
* Otherwise, return an empty String.  //  1405   fillcut
*/  //  1406   fillcut
static String fillCutBuffer(String cutee) {  //  1407   fillcut
if (IS_IN_WEBSTART) return "Cut buffer is unavailable under WebStart";  //  1408   webstart
try {  //  1409   fillcut
java.awt.datatransfer.StringSelection stringSelection  //  1410   fillcut
= new java.awt.datatransfer.StringSelection(cutee);  //  1411   fillcut
CLIPBOARD.setContents(stringSelection, null);  //  1412   fillcut
return "<br><font size='-1'>"  //  1413   fillcut
+ "(The link value is now in the cut buffer."  //  1414   fillcut
+ " You may paste it into your browesr.)</font>";  //  1415   fillcut
} catch (java.awt.HeadlessException  //  1416   fillcut
| java.security.AccessControlException ex) {  //  1417   fillcut
return "";  //  1418   fillcut
}  //  1419   fillcut
}  //  1420   fillcut
/**  //  1421   telluser
* Display a message in a dialog box.  //  1422   telluser
* The only user option is "OK" to dismiss the message.  //  1423   telluser
* @param title To appear in the title bar  //  1424   telluser
* @param message See askUser.  //  1425   telluser
*/  //  1426   telluser
public static void tellUser(String title, String message) {  //  1427   telluser
if (message != null)  //  1428   telluser
talkToUser(title, message, null, new String[]{"OK"});  //  1429   telluser
}  //  1430   telluser
/**  //  1431   askuser
* Popup a query box where a user fills in data and clicks a button.  //  1432   askuser
* If the body argument is a JTextComponent, a listener is added  //  1433   askuser
* so typing return will click the first response button.  //  1434   askuser
*  //  1435   askuser
* @param title String to appear in popup's title bar  //  1436   askuser
* @param message null or a message to display.  //  1437   askuser
* It will be wrapped in a JLabel and &lt;html&gt; tags,  //  1438   askuser
* including a &lt;font&gt; tag for a larger size.  //  1439   askuser
* @param body The contents or null. A JTextComponent  //  1440   askuser
* is processed as described above. A JComponent is displayed.  //  1441   askuser
* Other objects have their to String value displayed.  //  1442   askuser
* @param options An array of Strings  //  1443   askuser
* to be displayed in buttons beneath the body  //  1444   askuser
* @return The index of the selected button. Or -1 if for window dismissal.  //  1445   askuser
*/  //  1446   askuser
public static int talkToUser(String title,  //  1447   askuser
String message, Object body, Object[] options) {  //  1448   askuser
Object q = (message == null) ? null  //  1449   askuser
: new JLabel("<html><font size='+1'>"+message+"</font></html>");  //  1450   askuser
JOptionPane jop = new JOptionPane(new Object[]{q,body},  //  1451   askuser
JOptionPane.PLAIN_MESSAGE, JOptionPane.YES_NO_OPTION,  //  1452   askuser
null, options, options[0]);  //  1453   askuser
ICON64, options, options[0]);  //  1454   logoimages
JDialog dia = jop.createDialog(title);  //  1455   askuser
dia.setAlwaysOnTop(true);  //  1456   askuser
dia.setVisible(true); // (this returns only when user is done)  //  1457   askuser
dia.dispose();  //  1458   askuser
Object val = jop.getValue();  //  1459   askuser
if (val != null)  //  1460   askuser
for (int i = 0; i < options.length; i++)  //  1461   askuser
if (val.equals(options[i])) return i;  //  1462   askuser
return -1;  //  1463   askuser
}  //  1464   askuser
// – – – ---------- end FontViewer tellUser() ----------
// – – ---------- end FontViewer - Menu ----------
// – – FontViewer main() : the main program if FontViewer is not part of a larger application
///////////////////////////////////////  //  1468   backbone
// main()  //  1469   backbone
  //  1470   backbone
/** Run this application  //  1471   backbone
* @param args If given, each arg is the location of categories data.  //  1472   backbone
*/  //  1473   backbone
public static void main(String[] args) {  //  1474   backbone
FontViewer.printFontList(System.out);  //  1475   offline
// because they are basic documentation,  //  1476   adjustlaf
// tooltips need faster startup and longer duration  //  1477   adjustlaf
ToolTipManager tipman = ToolTipManager.sharedInstance();  //  1478   adjustlaf
tipman.setInitialDelay(375); // default is 750  //  1479   adjustlaf
tipman.setDismissDelay(6000); // default is 4000  //  1480   adjustlaf
// because askUser text is size='+1', buttons need to be bigger  //  1481   adjustlaf
// see http://stackoverflow.com/questions/4017042  //  1482   adjustlaf
UIManager.put("OptionPane.buttonFont",  //  1483   adjustlaf
new javax.swing.plaf.FontUIResource(FontViewer.BUTTON_FONT));  //  1484   adjustlaf
// If TAB to a button, ENTER should choose it  //  1485   adjustlaf
// https://stackoverflow.com/a/16923982/1614775  //  1486   adjustlaf
UIManager.put("Button.defaultButtonFollowsFocus", Boolean.TRUE);  //  1487   adjustlaf
  //  1488   adjustlaf
// create a FontViewer as a standalone app  //  1489   listwindow
final FontViewer viewer = FontViewer.create();  //  1490   listwindow
IOUtils.setDefaultJarMember(viewer); // (for "jardir:")  //  1491   catsmap
if (args.length != 0)  //  1492   catsload
viewer.loadCategories(args);  //  1493   catsload
else  //  1494   catsinit
viewer.loadInitialCategories();  //  1495   catsinit
viewer.setSaveFile(DEFAULT_CATSMAP);  //  1496   catssave
tellUser("Setting initial categories save file",  //  1497   telluser
viewer.setSaveFile(DEFAULT_CATSMAP));  //  1498   telluser
  //  1499   menu
SwingUtilities.invokeLater(()->{  //  1500   listwindow
JFrame frame = viewer.wrapWithJFrame();  //  1501   listwindow
frame.setBounds(40, 25, 725, 800);  //  1502   listwindow
frame.pack();  //  1503   listwindow
frame.setVisible(true);  //  1504   listwindow
});  //  1505   listwindow
} // end method main()  //  1506   backbone
// – – ---------- end FontViewer main() ----------
} // end class FontViewer  //  1508   backbone
Copyright © 2018 ZweiBieren, All rights reserved. Sep 18, 2018 15:34 GMT Page maintained by ZweiBieren