| | | | | // – FontViewer preface : comments, package, imports |
| | | | | /** FontViewer.java |
| | | | | * Show all the fonts that Java knows |
| | | | | * and display a sample string in each |
| | | | | * |
| | | | | * @author zweibieren |
| | | | | * Jan 13, 2010 5:25 PM |
| | | | | */ |
| | | | | |
| | | | | package com.physpics.apps.fontviewer; |
| | | | | |
| | | | | import com.physpics.tools.IOUtils; |
| | | | | import com.physpics.tools.StringMap; |
| | | | | import java.awt.*; |
| | | | | import java.awt.event.*; |
| | | | | import java.io.*; |
| | | | | import java.net.MalformedURLException; |
| | | | | import java.net.URI; |
| | | | | import java.net.URISyntaxException; |
| | | | | import java.net.URL; |
| | | | | import java.util.*; |
| | | | | import javax.swing.*; |
| | | | | import javax.swing.table.*; |
| | | | | import javax.swing.text.DefaultEditorKit; |
| | | | | /** |
| | | | | * View all fonts on the system. |
| | | | | * The window is laid out like this |
| | | | | * <pre> |
| | | | | * S C TTT |
| | | | | * LLLLLL! |
| | | | | * LLLLLL! |
| | | | | * LLLLLL! |
| | | | | * |
| | | | | * S, a JSpinner for choosing a font size |
| | | | | * C, a JComboBox for choosing one of the four styles |
| | | | | * TT, A JTextField for entering the text to be displayed in the fonts |
| | | | | * LLL, a JTable where each row has: |
| | | | | * "B" if font is bold |
| | | | | * "I" if font is italic |
| | | | | * font category, e.g., script or serif |
| | | | | * font name |
| | | | | * a check box to mark candidate fonts |
| | | | | * the T text displayed in the named font |
| | | | | * ! there is a vertical scrollbar for LLL |
| | | | | * </pre> |
| | | | | */ |
| | | | | public class FontViewer extends JPanel { |
| | | | | // – ---------- end FontViewer preface ---------- |
| | | | | // – – FontViewer variables : globals for the visible window components |
| | | | | static final Font[] FONT_LIST; // all Fonts known to local Java |
| | | | | static { |
| | | | | GraphicsEnvironment gEnv = |
| | | | | GraphicsEnvironment.getLocalGraphicsEnvironment(); |
| | | | | FONT_LIST = gEnv.getAllFonts(); |
| | | | | } |
| | | | | |
| | | | | static final boolean IS_IN_WEBSTART; |
| | | | | static { |
| | | | | boolean isin=false; |
| | | | | try { Class.forName("javax.jnlp.ServiceManager"); |
| | | | | isin = true; |
| | | | | } catch (ClassNotFoundException ex) { } |
| | | | | IS_IN_WEBSTART = isin; |
| | | | | } |
| | | | | |
| | | | | static final Toolkit TOOLKIT = Toolkit.getDefaultToolkit(); |
| | | | | static final java.awt.datatransfer.Clipboard CLIPBOARD |
| | | | | = TOOLKIT.getSystemClipboard(); |
| | | | | static final String SITE_URL |
| | | | | = "http://physpics.com/Java/apps/fontviewer"; |
| | | | | |
| | | | | static final Image LOGO16, LOGO32, LOGO64; |
| | | | | static Image getImage(String name) { |
| | | | | URL imageURL = FontViewer.class.getResource( |
| | | | | "/com/physpics/apps/fontviewer/images/"+name); |
| | | | | if (imageURL == null) { // not fully installed: try fetch from web |
| | | | | try { |
| | | | | imageURL = new URI(SITE_URL+"/images/"+name).toURL(); |
| | | | | } catch (URISyntaxException | MalformedURLException ex) { |
| | | | | imageURL = null; |
| | | | | } |
| | | | | } |
| | | | | return (imageURL == null ? null : TOOLKIT.createImage(imageURL)); |
| | | | | } |
| | | | | static { |
| | | | | LOGO16 = getImage("FV16.png"); |
| | | | | LOGO32 = getImage("FV32.png"); |
| | | | | LOGO64 = getImage("FV64.png"); |
| | | | | } |
| | | | | static final ImageIcon ICON64 |
| | | | | = (LOGO64 == null ? null : new ImageIcon(LOGO64)); |
| | | | | |
| | | | | // borders for cells in the table |
| | | | | static final javax.swing.border.Border CELL_BORDER |
| | | | | = BorderFactory.createEmptyBorder(0, 3, 0, 0); |
| | | | | static final javax.swing.border.Border BORDER_BLUE |
| | | | | = BorderFactory.createCompoundBorder( |
| | | | | BorderFactory.createLineBorder(Color.BLUE), |
| | | | | BorderFactory.createEmptyBorder(0, 2, 0, 0)); |
| | | | | |
| | | | | static final Font LABEL_FONT // default font for screen text |
| | | | | = new Font("Dialog", Font.PLAIN, 14); |
| | | | | static final Font STYLES_FONT |
| | | | | = new Font("Serif", Font.PLAIN, 14); |
| | | | | static final Font HEADER_FONT // font for column headers |
| | | | | = new Font("Dialog", Font.BOLD, 14); |
| | | | | static final Font BUTTON_FONT // font for dialog buttons |
| | | | | = new Font("Dialog", Font.BOLD, 14); |
| | | | | static final String[] STYLE_NAMES // for the style JComboBox |
| | | | | = {"Plain", "Bold", "Italic", "Bold Italic"}; |
| | | | | static final int[] STYLE_VALUES |
| | | | | = new int[] {Font.PLAIN, Font.BOLD, |
| | | | | Font.ITALIC, Font.BOLD | Font.ITALIC}; |
| | | | | final static int INITIALFONTSIZE = 12; // initial fontsize for samples |
| | | | | // a row is offscreen when no more than SLIVER pixels are visible |
| | | | | final static int SLIVER = 6; |
| | | | | // table row height is fontsize plus ADDFORROWHEIGHT |
| | | | | final static int ADDFORROWHEIGHT = 8; |
| | | | | static final String DEFAULT_SAMPLE // default string for fontsamples |
| | | | | = "The quick brown fox filled a theater " |
| | | | | + "playing his father's jazz improvisations."; |
| | | | | // This string has all letters, a ligature, an aposrophe, and a period. |
| | | | | // It includes common di- & trigrams: ed fi her ing is re ter the tion |
| | | | | // but lacks other common di- & trigrams: and ent es for hat of to |
| | | | | |
| | | | | public static final String CATS_FILE = "fontcategories.txt"; |
| | | | | public static final String DEFAULT_CATSMAP |
| | | | | = "userhome:/FontViewer/" + CATS_FILE; |
| | | | | public static final String HELP_URL = SITE_URL + "/FVHelp.html"; |
| | | | | |
| | | | | // components of the window |
| | | | | final JComboBox<String> styles = new JComboBox<>(STYLE_NAMES); |
| | | | | final SpinnerNumberModel sizeModel // font sizes range |
| | | | | = new SpinnerNumberModel(INITIALFONTSIZE, 6, 40, 4); |
| | | | | final JSpinner sizes; // choose from sizeModel |
| | | | | JTextField sampleEditor; |
| | | | | SampleText sampleEditor; // editor for the sample text |
| | | | | |
| | | | | // the fonts listing |
| | | | | FontsTable mainTable; // one line per font |
| | | | | final JScrollPane scrollTable; // scroller for mainTable |
| | | | | int styleColIndex = -1; // STYLECOL index for style flags, B/I |
| | | | | int catColIndex = -1; // CATCOL index for categories |
| | | | | int fontColIndex = -1; // FONTCOL index for font names |
| | | | | int chkColIndex = -1; // CHECKCOL index for checkboxes |
| | | | | int sampleColIndex = -1; // SAMPLECOL index for sample text |
| | | | | final String[] columnToolTips = new String[12]; |
| | | | | |
| | | | | // Category map : from font names to category text |
| | | | | StringMap catsMap = new StringMap(); // data for the categories column |
| | | | | ArrayList<String> categorySources |
| | | | | = new ArrayList<>(); // for 'about' dialog |
| | | | | Action saveAction, saveasAction; |
| | | | | // – – ---------- end FontViewer variables ---------- |
| | | | | // – – FontViewer - LifeCycle methods : create, focus, cleanup |
| | | | | // – – – FontViewer() constructor : create the window components |
| | | | | /** Construct a FontViewer. |
| | | | | * Instead of calling this constructor, |
| | | | | * call FontViewer.create {@link FontViewer#create()} |
| | | | | */ |
| | | | | private FontViewer() { |
| | | | | // begin autosaving the category data |
| | | | | catsMap.setAutosaving(true); |
| | | | | // make the size chooser for the top row |
| | | | | sizes = new JSpinner(sizeModel); |
| | | | | sizes.setFont(LABEL_FONT); |
| | | | | sizes.addChangeListener(e->mainTable.refontTheSamples()); |
| | | | | |
| | | | | // make the style chooser for the top row |
| | | | | styles.setFont(STYLES_FONT); |
| | | | | styles.addItemListener(e->mainTable.refontTheSamples()); |
| | | | | |
| | | | | // make the table showing fonts |
| | | | | scrollTable = new JScrollPane( |
| | | | | ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, |
| | | | | ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER) { |
| | | | | }; |
| | | | | mainTable = createFontsTable(); |
| | | | | scrollTable.setViewportView(mainTable); |
| | | | | scrollTable.setCorner(ScrollPaneConstants.UPPER_RIGHT_CORNER, |
| | | | | new JPanel(){ |
| | | | | { setOpaque(true); setBackground(mainTable.normalHeaderBkgd); } |
| | | | | @Override |
| | | | | protected void paintBorder(Graphics g) { |
| | | | | g.drawLine(0, getHeight() - 1, |
| | | | | getWidth(), getHeight() - 1); |
| | | | | } |
| | | | | }); |
| | | | | mainTable.reviseSamples(); |
| | | | | mainTable.refontTheSamples(); |
| | | | | } // end FontViewer(...) |
| | | | | // – – – ---------- end FontViewer() constructor ---------- |
| | | | | // – – – FontViewer create() : Construct and initialize a FontViewer |
| | | | | /** Construct a FontViewer and initialize. |
| | | | | * This method avoids the need to call methods |
| | | | | * of a partially constructed FontViewer. |
| | | | | * @return the created FontViewer |
| | | | | */ |
| | | | | public static FontViewer create() { |
| | | | | FontViewer viewer = new FontViewer(); |
| | | | | // make the sample text editor |
| | | | | viewer.sampleEditor = new JTextField(DEFAULT_SAMPLE); |
| | | | | viewer.sampleEditor = new JTextField(DEFAULT_SAMPLE); |
| | | | | viewer.sampleEditor = viewer.createSampleText(DEFAULT_SAMPLE); |
| | | | | // ENTER key in sampleEditor : so rebuild the SAMPLECOL |
| | | | | viewer.sampleEditor.addActionListener( |
| | | | | e ->viewer.mainTable.reviseSamples()); |
| | | | | |
| | | | | // put things in the window |
| | | | | viewer.setBorder(BorderFactory.createEmptyBorder(6, 6, 6, 6)); |
| | | | | viewer.setLayout(new BorderLayout(6, 6)); // gaps of size 6 |
| | | | | Box top = Box.createHorizontalBox(); |
| | | | | top.add(Box.createHorizontalStrut(12)); |
| | | | | viewer.sizes.setBorder(BorderFactory.createTitledBorder("Font Size")); |
| | | | | top.add(viewer.sizes); |
| | | | | top.add(createTitledWidget("Font Size", viewer.sizes, true)); |
| | | | | top.add(Box.createHorizontalStrut(12)); |
| | | | | viewer.styles.setBorder(BorderFactory.createTitledBorder("Font Style")); |
| | | | | top.add(viewer.styles); |
| | | | | top.add(createTitledWidget("Font Style", viewer.styles, true)); |
| | | | | top.add(Box.createHorizontalStrut(12)); |
| | | | | top.add(viewer.sampleEditor); |
| | | | | top.add(createTitledWidget("Sample Text", |
| | | | | viewer.sampleEditor, false)); |
| | | | | top.add(Box.createHorizontalStrut(12)); |
| | | | | |
| | | | | viewer.add(top, BorderLayout.NORTH); |
| | | | | viewer.add(viewer.mainTable, BorderLayout.CENTER); |
| | | | | viewer.add(viewer.scrollTable, BorderLayout.CENTER); |
| | | | | return viewer; |
| | | | | } // end create(...) |
| | | | | // – – – ---------- end FontViewer create() ---------- |
| | | | | // – – – FontViewer printFontList() : print fontnames |
| | | | | /** |
| | | | | * Print the list of fonts |
| | | | | * |
| | | | | * @param ps where to send the output |
| | | | | */ |
| | | | | static public void printFontList(PrintStream ps) { |
| | | | | for (Font f : FontViewer.FONT_LIST) |
| | | | | ps.println(f.getFontName()); |
| | | | | } |
| | | | | // – – – ---------- end FontViewer printFontList() ---------- |
| | | | | // – – – FontViewer closePanel() : Save before kill window |
| | | | | /** Check that category values are saved before closing window. |
| | | | | * @return True to proceed with window closure |
| | | | | */ |
| | | | | public boolean closePanel() { |
| | | | | mainTable.finishEditing(); |
| | | | | return true; // always exit |
| | | | | if (catsMap.flush()) // no pending change : exit |
| | | | | return true; |
| | | | | JButton help = new JButton("Help"); |
| | | | | help.setFont(BUTTON_FONT); |
| | | | | help.addActionListener(butev->browseGuide("categoriesfile")); |
| | | | | Object[] buttons = new Object[]{"Save & Exit", |
| | | | | "Exit without save", "Do not exit", help}; |
| | | | | JTextField dest = new JTextField(DEFAULT_CATSMAP, 40); |
| | | | | int opt = talkToUser("Unsaved Category Data", |
| | | | | "Data unsaved. Would you like to store it? If so, where?", |
| | | | | dest, buttons); |
| | | | | if (opt == 1) return true; // exit wo/ save |
| | | | | if (opt != 0) return false; // other cases, do not exit |
| | | | | // case 0, try to save |
| | | | | String msg = setSaveFile(dest.getText()); |
| | | | | if (msg != null) { |
| | | | | tellUser("Final categories save failed", msg); |
| | | | | return false; |
| | | | | } |
| | | | | return true; |
| | | | | } |
| | | | | // – – – ---------- end FontViewer closePanel() ---------- |
| | | | | // – – – FontViewer wrapWithJFrame() : create a JFrame and insert the viewer in it |
| | | | | /** |
| | | | | * Make this FontViewer be a standalone application. |
| | | | | * Add icons, menus, and close-window behavior. |
| | | | | * @return A new JFrame holding this FontViwer |
| | | | | */ |
| | | | | public JFrame wrapWithJFrame() { |
| | | | | final JFrame frame = new JFrame(" Font Viewer"); |
| | | | | frame.setContentPane(this); |
| | | | | if (LOGO64 != null) { |
| | | | | java.util.ArrayList<Image> icons = new java.util.ArrayList<>(); |
| | | | | icons.add(LOGO16); |
| | | | | icons.add(LOGO32); |
| | | | | icons.add(LOGO64); |
| | | | | frame.setIconImages(icons); |
| | | | | } |
| | | | | JMenuBar menuBar = new JMenuBar(); |
| | | | | insertMenuItems(menuBar); |
| | | | | frame.setJMenuBar(menuBar); |
| | | | | // Deal with external window closure |
| | | | | frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); |
| | | | | frame.setDefaultCloseOperation( |
| | | | | WindowConstants.DO_NOTHING_ON_CLOSE); |
| | | | | frame.addWindowListener(new WindowAdapter() { |
| | | | | @Override |
| | | | | public void windowClosing(WindowEvent we) { |
| | | | | if (closePanel()) |
| | | | | we.getWindow().dispose(); |
| | | | | } |
| | | | | }); |
| | | | | return frame; |
| | | | | } |
| | | | | // – – – ---------- end FontViewer wrapWithJFrame() ---------- |
| | | | | // – – ---------- end FontViewer - LifeCycle methods ---------- |
| | | | | // – – FontViewer enclosed classes : pieces of the visible window |
| | | | | //////////////////////////////// |
| | | | | // FONTVIEWER ENCLOSED CLASSES |
| | | | | // – – – TitledWidget class : A JPanel with a title at top and a widget within |
| | | | | /** |
| | | | | * Create a JPanel containing a widget with a title above it. |
| | | | | * This class is here because look-and-feel implementations |
| | | | | * differ too much in borders around JComboBox and JSpinner. |
| | | | | */ |
| | | | | public static class TitledWidget extends JPanel { |
| | | | | /** |
| | | | | * Create a TitledWidget |
| | | | | */ |
| | | | | private TitledWidget() { |
| | | | | } |
| | | | | } |
| | | | | /** |
| | | | | * Create a TitledWidget with padding on the right and below. |
| | | | | * |
| | | | | * @param t Title string to display |
| | | | | * @param widget The widget to border |
| | | | | * @param fixedWidth Prevent width change |
| | | | | * @return the new widget |
| | | | | */ |
| | | | | public static TitledWidget createTitledWidget(String t, |
| | | | | JComponent widget, boolean fixedWidth) { |
| | | | | TitledWidget titler = new TitledWidget(); |
| | | | | titler.setLayout(new BorderLayout()); // layout for JPanel |
| | | | | JLabel lbl = new JLabel(" " + t); // to line up left edges |
| | | | | lbl.setForeground(Color.BLUE); |
| | | | | titler.add(lbl, BorderLayout.NORTH); |
| | | | | titler.add(widget, BorderLayout.CENTER); |
| | | | | if (fixedWidth) { |
| | | | | Dimension wpref = widget.getPreferredSize(); |
| | | | | Dimension lpref = lbl.getPreferredSize(); |
| | | | | Dimension pref = new Dimension( |
| | | | | Math.max(wpref.width, lpref.width), |
| | | | | wpref.height + lpref.height); |
| | | | | titler.setMaximumSize(pref); |
| | | | | } |
| | | | | return titler; |
| | | | | } |
| | | | | // – – – ---------- end TitledWidget class ---------- |
| | | | | // – – – SampleText class : a JTextField with application-specific event handlers and a reset Action |
| | | | | |
| | | | | /** Construct and initialize a SampleText. This method avoids |
| | | | | * calling methods on partially constructed objects. |
| | | | | * @param starterText initial text displayed |
| | | | | * @return the SampleText |
| | | | | */ |
| | | | | public SampleText createSampleText(String starterText) { |
| | | | | final SampleText text = new SampleText(starterText); |
| | | | | text.setFont(LABEL_FONT); |
| | | | | text.setMargin(new Insets(3, 8, 3, 0)); |
| | | | | |
| | | | | // focus loss from edittext : so rebuild all samples in table |
| | | | | text.addFocusListener(new FocusAdapter() { |
| | | | | @Override |
| | | | | public void focusLost(FocusEvent e) { |
| | | | | text.fireActionPerformed(); |
| | | | | } |
| | | | | }); |
| | | | | // define an Action that will reset the sample text |
| | | | | text.resetAction = createAction(FontViewer.this, "_Reset sample", |
| | | | | "Reset the sample text to its original value", |
| | | | | null, // no window-wide accelerator key |
| | | | | e->{ text.setText(DEFAULT_SAMPLE); |
| | | | | text.fireActionPerformed(); } // revise samples |
| | | | | ); |
| | | | | // install an accelerator key restricted to this sample text |
| | | | | KeyStroke ctlR = KeyStroke.getKeyStroke("control pressed R"); |
| | | | | String resetCommand = "reset sample text"; |
| | | | | text.getInputMap(JComponent.WHEN_FOCUSED).put(ctlR, resetCommand); |
| | | | | text.getActionMap().put(resetCommand, text.resetAction); |
| | | | | |
| | | | | // define and utilize a popup menu with only an item for reset |
| | | | | JPopupMenu resetMenu = new JPopupMenu(); |
| | | | | JMenuItem resetItem = new JMenuItem(text.resetAction); |
| | | | | resetItem.setAccelerator(ctlR); // display "Ctrl+R" in menu |
| | | | | resetMenu.add(resetItem); |
| | | | | text.setComponentPopupMenu(resetMenu); |
| | | | | |
| | | | | return text; |
| | | | | } |
| | | | | /** |
| | | | | * A viewer/editor for the sample text to be shown in each font. |
| | | | | * Little more than a JTextField with an Action to reset the text |
| | | | | * to its default value. |
| | | | | */ |
| | | | | public class SampleText extends JTextField { |
| | | | | public Action resetAction = null; |
| | | | | String currentSample; |
| | | | | |
| | | | | /** Override firing of actionPerformed |
| | | | | * so it only fires when the text has actually changed. |
| | | | | */ |
| | | | | @Override |
| | | | | protected void fireActionPerformed() { |
| | | | | if (getText().equals(currentSample)) return; |
| | | | | currentSample = getText(); |
| | | | | super.fireActionPerformed(); |
| | | | | } |
| | | | | /** |
| | | | | * Internal constructor for SampleText. Instead of calling this, |
| | | | | * clients should invoke FontViewer.createSampleText. |
| | | | | * @param starterText initial text to display in box |
| | | | | */ |
| | | | | private SampleText(String starterText) { |
| | | | | super(starterText, 50); |
| | | | | currentSample = starterText; |
| | | | | } |
| | | | | } |
| | | | | // – – – ---------- end SampleText class ---------- |
| | | | | // – – – FontViewer createFontsTable() : Create the table and add its columns |
| | | | | /////////////////////////////////////// |
| | | | | // FontsTable |
| | | | | |
| | | | | /** @return the main font table */ |
| | | | | public FontsTable getMainTable() { return mainTable; } |
| | | | | |
| | | | | /** |
| | | | | * Create and initialize a FontsTable. |
| | | | | * (This method avoids calling overridable methods from the constructor.) |
| | | | | * @return the initialized FontsTable |
| | | | | */ |
| | | | | private FontsTable createFontsTable() { |
| | | | | FontsTable table = new FontsTable(); |
| | | | | table.setFillsViewportHeight(true); |
| | | | | table.setCellSelectionEnabled(true); |
| | | | | table.setGridColor(new Color(200, 240, 255)); |
| | | | | table.getTableHeader().setBackground(table.normalHeaderBkgd); |
| | | | | table.setRowHeight(INITIALFONTSIZE + ADDFORROWHEIGHT); |
| | | | | table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); |
| | | | | |
| | | | | table.addKeyListener(new KeyAdapter() { |
| | | | | @Override |
| | | | | public void keyPressed(KeyEvent e) { |
| | | | | int row = table.getSelectedRow(); |
| | | | | if (row == -1) return; |
| | | | | row = table.convertRowIndexToModel(row); |
| | | | | int col = table.getSelectedColumn(); |
| | | | | col = table.convertColumnIndexToModel(col); |
| | | | | if (col != styleColIndex) return; |
| | | | | TableModel model = table.getModel(); |
| | | | | String s = (String)model.getValueAt(row, styleColIndex); |
| | | | | int slen = s.length(); |
| | | | | // s is exactly one of "" "B" "I" "BI" |
| | | | | switch (e.getKeyCode()) { |
| | | | | case KeyEvent.VK_B: |
| | | | | if (slen > 0 && s.startsWith("B")) |
| | | | | s = s.substring(1); |
| | | | | else s = "B" + s; |
| | | | | break; |
| | | | | case KeyEvent.VK_I: |
| | | | | if (slen > 0 && s.endsWith("I")) |
| | | | | s = s.substring(0, slen - 1); |
| | | | | else s += "I"; |
| | | | | break; |
| | | | | case KeyEvent.VK_BACK_SPACE: |
| | | | | if (slen > 0) s = s.substring(0, slen - 1); |
| | | | | break; |
| | | | | case KeyEvent.VK_DELETE: |
| | | | | if (slen > 0) s = s.substring(1); |
| | | | | break; |
| | | | | default: |
| | | | | if (Character.isLetterOrDigit(e.getKeyCode())) |
| | | | | e.consume(); // ignore letters/digits in styles col |
| | | | | return; |
| | | | | } |
| | | | | // now s has the new value |
| | | | | model.setValueAt(s, row, styleColIndex); |
| | | | | e.consume(); |
| | | | | } |
| | | | | }); |
| | | | | |
| | | | | // enable default sorting the table by column data |
| | | | | table.setAutoCreateRowSorter(true); |
| | | | | |
| | | | | // enable sorting the table by column data |
| | | | | table.setRowSorter(table.new FTRowSorter(table.tableData)); |
| | | | | Comparator<String> stringComparator = |
| | | | | (s0,s1)->s0.length() == 0 |
| | | | | ? (s1.length() == 0 ? 0 : +1) // non-blanks first |
| | | | | : (s1.length() == 0 ? -1 : s0.compareToIgnoreCase(s1)); |
| | | | | Comparator<Boolean> booleanComparator = |
| | | | | (o1,o2)->o2.compareTo(o1); |
| | | | | Comparator<FontsTable.SampleFont> fontComparator = |
| | | | | (f0,f1)->f0.getWidth()-f1.getWidth(); |
| | | | | // – – – – createFontsTable() : configure the TableModel with five columns |
| | | | | // Configure the columns : data, widths, renderers, and editors |
| | | | | // by setting limits on the widths of the first three columns, |
| | | | | // all excess space is allocated to the sample text column |
| | | | | |
| | | | | // we will create our own TableColumns |
| | | | | table.setAutoCreateColumnsFromModel(false); |
| | | | | // tell this JTable about the model |
| | | | | table.setModel(table.tableData); |
| | | | | |
| | | | | // STYLECOL - B for bold and I for italic |
| | | | | styleColIndex = table.appendColumn("BI", table.styleColumn, |
| | | | | 32, 32, 32, |
| | | | | "B-old or I-talic; click header to sort, " |
| | | | | + "with non-blanks at the top", |
| | | | | stringComparator, |
| | | | | null, null); |
| | | | | table.new FTStringRenderer(STYLES_FONT), null); |
| | | | | // CATCOL - the categories column |
| | | | | TableCellEditor catEditor = table.new FTStringEditor(); |
| | | | | catColIndex = table.appendColumn("Category", table.categoryColumn, |
| | | | | 25, 75, 150, |
| | | | | "Font category or user notes: Click header to sort", |
| | | | | stringComparator, |
| | | | | null, |
| | | | | table.new FTStringRenderer(LABEL_FONT), |
| | | | | null); |
| | | | | catEditor); |
| | | | | |
| | | | | // FONTCOL - column of font names |
| | | | | fontColIndex = table.appendColumn("Font Name", table.FontColumn, |
| | | | | 75, 200, 300, |
| | | | | "Sort by name. Click a name to copy it to the cut buffer.", |
| | | | | stringComparator, |
| | | | | null, null); |
| | | | | table.new FTStringRenderer(LABEL_FONT), null); |
| | | | | |
| | | | | // CHECKCOL - a checkbox to select fonts |
| | | | | chkColIndex = table.appendColumn("Ck", table.checkColumn, |
| | | | | 26, 26, 26, |
| | | | | "Move checked lines to the top", |
| | | | | booleanComparator, |
| | | | | table.getDefaultRenderer(Boolean.class), |
| | | | | table.getDefaultEditor(Boolean.class)); |
| | | | | |
| | | | | // SAMPLECOL - sample text in this row's font |
| | | | | sampleColIndex = table.appendColumn("Sample written in named font", |
| | | | | table.sampleColumn, 75, 300, 10000, |
| | | | | "Sample in the line's font; " |
| | | | | + "click header to sort by width on screen", |
| | | | | null, |
| | | | | fontComparator, |
| | | | | table.new FTFontRenderer(), null); |
| | | | | |
| | | | | // set a single tooltip for all headers |
| | | | | table.getTableHeader().setToolTipText("<html>" |
| | | | | + "To compare fonts, click under Ck & sort.<br> " |
| | | | | + "To sort on any column, click its header.</html>"); |
| | | | | // set a renderer for column headings |
| | | | | table.getTableHeader() |
| | | | | .setDefaultRenderer(table.new FTHeaderRenderer()); |
| | | | | // – – – – ---------- end createFontsTable() ---------- |
| | | | | // after tableData initialized, start listening for changes |
| | | | | table.tableData.addTableModelListener(table.modelListener); |
| | | | | return table; |
| | | | | } |
| | | | | // – – – ---------- end FontViewer createFontsTable() ---------- |
| | | | | // – – – FontsTable class : the central JTable for the application |
| | | | | /** |
| | | | | * The table of font names and sample strings. |
| | | | | */ |
| | | | | protected class FontsTable extends JTable { |
| | | | | DefaultTableModel tableData = new DefaultTableModel() { |
| | | | | @Override |
| | | | | // STYLECOL, CATCOL, and CHECKCOL can be edited |
| | | | | public boolean isCellEditable(int row, int col) { |
| | | | | return false |
| | | | | || col == catColIndex |
| | | | | || col == chkColIndex |
| | | | | ; |
| | | | | } |
| | | | | }; |
| | | | | |
| | | | | javax.swing.event.TableModelListener modelListener = e-> { |
| | | | | if ((e.getColumn() == catColIndex |
| | | | | || e.getColumn() == styleColIndex |
| | | | | ) && e.getType() |
| | | | | == javax.swing.event.TableModelEvent.UPDATE) { |
| | | | | int row = e.getFirstRow(); |
| | | | | String key = FONT_LIST[row].getFontName(); |
| | | | | TableModel mod = (TableModel)e.getSource(); |
| | | | | String style = (String) mod.getValueAt(row, styleColIndex); |
| | | | | String cat = (String) mod.getValueAt(row, catColIndex); |
| | | | | catsMap.put(key, cat); |
| | | | | catsMap.put(key, style+";"+cat); |
| | | | | } |
| | | | | }; |
| | | | | |
| | | | | // values to create the columns |
| | | | | String[] styleColumn; // STYLECOL |
| | | | | String[] categoryColumn; // CATCOL |
| | | | | final String[] FontColumn; // FONTCOL |
| | | | | Boolean[] checkColumn; // CHECKCOL |
| | | | | SampleFont[] sampleColumn; // SAMPLECOL |
| | | | | |
| | | | | // colors for header background |
| | | | | Color normalHeaderBkgd = new Color(255, 255, 0xCE); |
| | | | | Color sortingHeaderBkgd = new Color(255, 0x99, 0x33); |
| | | | | |
| | | | | // backdoor value for header cell renderer |
| | | | | int nowSorting = -1; // what column (in Model) is being sorted |
| | | | | |
| | | | | // – – – – FontsTable() constructor : build the screen contents of the table |
| | | | | /** |
| | | | | * Construct an uninitialized Fonts table. |
| | | | | * Call createFontable instead of this. |
| | | | | */ |
| | | | | private FontsTable() { |
| | | | | // values to create the columns |
| | | | | styleColumn = new String[FONT_LIST.length]; // STYLECOL |
| | | | | categoryColumn = new String[FONT_LIST.length]; // CATCOL |
| | | | | FontColumn = new String[FONT_LIST.length]; // FONTCOL |
| | | | | checkColumn = new Boolean[FONT_LIST.length]; // CHECKCOL |
| | | | | sampleColumn = new SampleFont[FONT_LIST.length]; // SAMPLECOL |
| | | | | |
| | | | | for (int inx = 0; inx < FONT_LIST.length; inx++) { |
| | | | | String fontname = FONT_LIST[inx].getFontName(); |
| | | | | int hasBold = (fontname.contains("Bold") |
| | | | | || fontname.contains("bold")) ? 1 : 0; |
| | | | | int hasItal = (fontname.contains("Italic") |
| | | | | || fontname.contains("italic") |
| | | | | || fontname.contains("blique")) ? 2 : 0; |
| | | | | styleColumn[inx] = new String[]{"", "B", "I", "BI"} |
| | | | | [hasBold+hasItal]; // STYLECOL |
| | | | | categoryColumn[inx] = ""; // CATCOL |
| | | | | FontColumn[inx] = fontname; // FONTCOL |
| | | | | checkColumn[inx] = false; // CHECKCOL |
| | | | | sampleColumn[inx] // SAMPLECOL |
| | | | | = new SampleFont(FONT_LIST[inx]); |
| | | | | } |
| | | | | |
| | | | | } // end FontsTable constructor |
| | | | | // – – – – ---------- end FontsTable() constructor ---------- |
| | | | | // – – – – FontsTable methods |
| | | | | ///////////////////////////////////////////// |
| | | | | // FontsTable methods |
| | | | | // – – – – – FontTable methods : appendColumn and createDefaultTableHeader |
| | | | | /** |
| | | | | * Append a column to the TableModel and the TableColumnModel. |
| | | | | * Convenience method for building the table. |
| | | | | */ |
| | | | | final int appendColumn(String columnName, Object[] data, int minW, |
| | | | | int prefW, int maxW, |
| | | | | String colTooltip, |
| | | | | Comparator comp, |
| | | | | TableCellRenderer render, TableCellEditor edit) { |
| | | | | int index = tableData.getColumnCount(); |
| | | | | tableData.addColumn(columnName, data); |
| | | | | TableColumn col = new TableColumn(index, prefW, render, edit); |
| | | | | col.setMinWidth(minW); |
| | | | | col.setMaxWidth(maxW); |
| | | | | columnToolTips[index] = colTooltip; |
| | | | | ((FTRowSorter)getRowSorter()).setComparator(index, comp); |
| | | | | ((FTRowSorter)getRowSorter()).setSortable(index, comp!=null); |
| | | | | addColumn(col); |
| | | | | return index; |
| | | | | } |
| | | | | |
| | | | | /** Implement table header tooltips. |
| | | | | https://docs.oracle.com/javase/tutorial/uiswing/ |
| | | | | components/table.html#headertooltip |
| | | | | * @return a JTableHeader that overrides getToolTipText |
| | | | | */ |
| | | | | @Override |
| | | | | protected JTableHeader createDefaultTableHeader() { |
| | | | | return new JTableHeader(columnModel) { |
| | | | | @Override |
| | | | | public String getToolTipText(MouseEvent e) { |
| | | | | java.awt.Point p = e.getPoint(); |
| | | | | int index = columnModel.getColumnIndexAtX(p.x); |
| | | | | int realIndex = |
| | | | | columnModel.getColumn(index).getModelIndex(); |
| | | | | return columnToolTips[realIndex]; |
| | | | | } |
| | | | | }; |
| | | | | } |
| | | | | // – – – – – ---------- end FontTable methods ---------- |
| | | | | // – – – – – FontsTable replaceCategories() : replace the categories column from a StringMap |
| | | | | /** |
| | | | | * Replace the categories data from the new sample map. |
| | | | | * Handles notification of the change. |
| | | | | * Does nothing about former values. |
| | | | | * @param categories The new data |
| | | | | */ |
| | | | | private void replaceCategories() { |
| | | | | tableData.removeTableModelListener(modelListener); |
| | | | | // replace contents of each row in categories column |
| | | | | // by lookup of font name for that row |
| | | | | for (int inx = 0; inx < FONT_LIST.length; inx++) { |
| | | | | String fontname = FONT_LIST[inx].getFontName(); |
| | | | | String catStr = catsMap.get(fontname); |
| | | | | if (catStr == null) |
| | | | | catStr = ""; |
| | | | | else { |
| | | | | // for FontViewer, a StringMap value is style;category |
| | | | | // where style is one of "", "B", "I" or "BI" |
| | | | | // extract style code from front of catStr |
| | | | | int semiloc = catStr.indexOf(";"); |
| | | | | if (semiloc >= 0) { |
| | | | | String styleStr = catStr.substring(0, semiloc).trim(); |
| | | | | mainTable.tableData |
| | | | | .setValueAt(styleStr, inx, styleColIndex); |
| | | | | catStr = catStr.substring(semiloc + 1).trim(); |
| | | | | } |
| | | | | } |
| | | | | mainTable.tableData.setValueAt(catStr, inx, catColIndex); |
| | | | | } |
| | | | | tableData.addTableModelListener(modelListener); |
| | | | | tableData.fireTableDataChanged(); |
| | | | | } |
| | | | | // – – – – – ---------- end FontsTable replaceCategories() ---------- |
| | | | | // – – – – – FontsTable refontTheSamples() : when the size or style changes, revise the fonts for all samples |
| | | | | /** |
| | | | | * When the size or style changes, revise the fonts for all samples. |
| | | | | */ |
| | | | | private void refontTheSamples() { |
| | | | | // get current status |
| | | | | if (sampleColIndex < 0) return; |
| | | | | int selectedRow = getSelectedRow(); |
| | | | | int selectedCol = getSelectedColumn(); |
| | | | | int oldRowHeight = getRowHeight(); |
| | | | | // record viewRect before it is overwritten by changeSelection() |
| | | | | JViewport vp = scrollTable.getViewport(); |
| | | | | Rectangle viewRect = vp.getViewRect(); // the visible rectangle |
| | | | | // fetch the parameters for new fonts |
| | | | | int fontsize = INITIALFONTSIZE; |
| | | | | int fontsize = sizeModel.getNumber().intValue(); |
| | | | | int styleInt = Font.PLAIN; |
| | | | | int styleInt = STYLE_VALUES[styles.getSelectedIndex()]; |
| | | | | |
| | | | | // replace the fonts in all samples |
| | | | | for (int inx = 0; inx < FONT_LIST.length; inx++) { |
| | | | | Font newf = new SampleFont(FONT_LIST[inx] |
| | | | | .deriveFont(styleInt, (float)fontsize)); |
| | | | | tableData.setValueAt(newf, inx, sampleColIndex); |
| | | | | } |
| | | | | |
| | | | | // choose a table row height appropriate to fontsize |
| | | | | int newRowHeight = Math.max(INITIALFONTSIZE - 4, fontsize) |
| | | | | + ADDFORROWHEIGHT; |
| | | | | |
| | | | | // if selected cell was visible, |
| | | | | // scroll so it shows close to where it was |
| | | | | // but if it was offscreen, |
| | | | | // scroll to keep the top line where it was |
| | | | | int toprow; |
| | | | | if (selectedRow == -1) |
| | | | | // no selection, retain existing top row |
| | | | | toprow = rowAtPoint(new Point(0, viewRect.y)); |
| | | | | else { |
| | | | | // return the focus to the selected cell, if any |
| | | | | // (JTable turns on autoscrolls, but it scrolls twice: |
| | | | | // here and in setViewPosition() below) |
| | | | | setAutoscrolls(false); // momentarily block autoscrolls |
| | | | | changeSelection(selectedRow, selectedCol, false, false); |
| | | | | setAutoscrolls(true); // re-enable autoscrolls |
| | | | | |
| | | | | Rectangle s // the location of the selectedRow |
| | | | | = getCellRect(selectedRow, 0, true); |
| | | | | if (s.y + s.height - SLIVER < viewRect.y |
| | | | | || s.y > viewRect.y + viewRect.height - SLIVER) |
| | | | | // the selection was not in the view, retain existing top row |
| | | | | toprow = rowAtPoint(new Point(0, viewRect.y)); |
| | | | | else { |
| | | | | // scroll selectedRow to near its old position on the screen |
| | | | | int offset = s.y - viewRect.y; |
| | | | | toprow = selectedRow |
| | | | | - (offset-SLIVER) / (INITIALFONTSIZE+ADDFORROWHEIGHT); |
| | | | | toprow = selectedRow - (offset - SLIVER) / newRowHeight; |
| | | | | } |
| | | | | } |
| | | | | setRowHeight(newRowHeight); // change view geometry ! |
| | | | | int yTop = getCellRect(toprow, 0, true).y; |
| | | | | vp.setViewPosition(new Point(0, yTop)); |
| | | | | |
| | | | | // the focus has moved to the sizes or styles widget |
| | | | | // return the focus to the table |
| | | | | requestFocusInWindow(); |
| | | | | |
| | | | | repaint(); // re-render all (to get new samples displayed) |
| | | | | } // end refontTheSamples |
| | | | | // – – – – – ---------- 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. */ |
| | | | | private void reviseSamples() { |
| | | | | for (int inx = 0; inx < FONT_LIST.length; inx++) // cause repaint |
| | | | | tableData.fireTableCellUpdated(inx, sampleColIndex); |
| | | | | } |
| | | | | // – – – – – ---------- end FontsTable reviseSamples() ---------- |
| | | | | // – – – – – FontsTable finishEditing() : called when any open cell editor should be closed |
| | | | | /** Close any open cell editor */ |
| | | | | public void finishEditing() { |
| | | | | CellEditor ed = getCellEditor(); |
| | | | | if (ed != null) |
| | | | | ed.stopCellEditing(); |
| | | | | } |
| | | | | // – – – – – ---------- end FontsTable finishEditing() ---------- |
| | | | | // – – – – ---------- end FontsTable methods ---------- |
| | | | | // – – – – FontsTable : support classes |
| | | | | ////////////////////////////////////////// |
| | | | | // Classes for FontsTable |
| | | | | |
| | | | | // – – – – – FontsTable SampleFont : a Font subclass with the right toString() value |
| | | | | /** |
| | | | | * The samples column is an array of Font objects, |
| | | | | * If the user types ctl-C to copy a samples column cell, |
| | | | | * the value later pasted will be from Font.toString(). Not useful. |
| | | | | * |
| | | | | * SampleFont.toString() returns the String that actually appears |
| | | | | * |
| | | | | */ |
| | | | | class SampleFont extends Font { |
| | | | | int width = -1; |
| | | | | java.awt.font.FontRenderContext renderContext |
| | | | | = new java.awt.image.BufferedImage(1, 1, |
| | | | | java.awt.image.BufferedImage.TYPE_4BYTE_ABGR) |
| | | | | .getGraphics().getFontMetrics() |
| | | | | .getFontRenderContext(); |
| | | | | /** |
| | | | | * Constructor from a Font. The default consructor cannot be used |
| | | | | * because Font does not have a constructor with no arguments. |
| | | | | * @param f The Font for this sample. |
| | | | | */ |
| | | | | public SampleFont(Font f) { |
| | | | | super(f); |
| | | | | } |
| | | | | /** @return the sample string */ |
| | | | | @Override public String toString() { |
| | | | | return sampleEditor.getText(); |
| | | | | } |
| | | | | /** |
| | | | | * @return Screen width of sample text in this font. |
| | | | | * For sorting the rows by sample width |
| | | | | */ |
| | | | | public int getWidth() { |
| | | | | return (width >= 0) ? width |
| | | | | : (width = (int)getStringBounds( |
| | | | | sampleEditor.getText(), renderContext) |
| | | | | .getWidth()); |
| | | | | } |
| | | | | } |
| | | | | // – – – – – ---------- end FontsTable SampleFont ---------- |
| | | | | // – – – – – FontsTable FTStringRenderer : a Cell renderer for categories and fontnames |
| | | | | /** Render a table cell with my chosen font and border. */ |
| | | | | class FTStringRenderer extends DefaultTableCellRenderer { |
| | | | | Font columnFont; |
| | | | | /** |
| | | | | * Constructor to specify a font for the column contents. |
| | | | | * @param f the font, STYLES_FONT or LABEL_FONT |
| | | | | */ |
| | | | | FTStringRenderer(Font f) { |
| | | | | super(); |
| | | | | columnFont = f; |
| | | | | } |
| | | | | /** Tailor this CellRenderer as appropriate. */ |
| | | | | @Override |
| | | | | public Component getTableCellRendererComponent( |
| | | | | JTable table, |
| | | | | Object value, // the Font for this sample cell |
| | | | | boolean isSelected, boolean hasFocus, |
| | | | | int row, int column) { |
| | | | | if (hasFocus && column == fontColIndex) |
| | | | | fillCutBuffer((String)value); |
| | | | | setText((String)value); |
| | | | | setFont(columnFont); |
| | | | | setBorder(hasFocus ? BORDER_BLUE : CELL_BORDER); |
| | | | | setForeground(hasFocus ? Color.BLUE : Color.BLACK); |
| | | | | return this; |
| | | | | } |
| | | | | } |
| | | | | |
| | | | | // – – – – – ---------- end FontsTable FTStringRenderer ---------- |
| | | | | // – – – – – FontsTable FTStringEditor : an editor for styles and categories |
| | | | | /** Edit a table cell with my chosen font and border. */ |
| | | | | class FTStringEditor extends AbstractCellEditor |
| | | | | implements TableCellEditor { |
| | | | | /** |
| | | | | * An implementation of DefaultCaret that can skip a setDot. |
| | | | | * This makes it possible to select the entire contents of a cell |
| | | | | * when first opening it for editing. |
| | | | | */ |
| | | | | class SkippyCaret extends javax.swing.text.DefaultCaret { |
| | | | | boolean skipOne = false; |
| | | | | @Override |
| | | | | public void setDot(int dot, |
| | | | | javax.swing.text.Position.Bias dotBias) { |
| | | | | if (skipOne) skipOne = false; |
| | | | | else super.setDot(dot, dotBias); |
| | | | | } |
| | | | | }; |
| | | | | final SkippyCaret localCaret = new SkippyCaret(); |
| | | | | final JTextField component = new JTextField() {{ |
| | | | | setForeground(Color.RED); |
| | | | | setFont(LABEL_FONT); |
| | | | | setCaret(localCaret); |
| | | | | }}; |
| | | | | @Override |
| | | | | public Component getTableCellEditorComponent( |
| | | | | JTable table, |
| | | | | Object value, |
| | | | | boolean isSelected, |
| | | | | int row, |
| | | | | int column) { |
| | | | | component.setText((String) value); |
| | | | | localCaret.setDot(0); |
| | | | | localCaret.moveDot(((String)value).length()); |
| | | | | localCaret.skipOne = true; |
| | | | | return component; |
| | | | | } |
| | | | | @Override |
| | | | | public Object getCellEditorValue() { |
| | | | | return component.getText(); |
| | | | | } |
| | | | | } |
| | | | | // – – – – – ---------- end FontsTable FTStringEditor ---------- |
| | | | | // – – – – – FontsTable FTFontRenderer : render the sample text; the data is actually the Font object |
| | | | | /** render the samples column; |
| | | | | * show the edittext in the row's named Font. */ |
| | | | | class FTFontRenderer extends DefaultTableCellRenderer { |
| | | | | @Override |
| | | | | public Component getTableCellRendererComponent( |
| | | | | JTable table, |
| | | | | Object value, // the Font for this sample cell |
| | | | | boolean isSelected, boolean hasFocus, |
| | | | | int row, int column) { // these two parameters are ignored |
| | | | | JLabel renderer = this; //DefaultTableCellRenderer extends JLabel |
| | | | | renderer.setText(sampleEditor.getText()); |
| | | | | renderer.setFont((Font)value); |
| | | | | renderer.setBorder(hasFocus ? BORDER_BLUE : CELL_BORDER); |
| | | | | renderer.setForeground(hasFocus ? Color.BLUE : Color.BLACK); |
| | | | | return renderer; |
| | | | | } |
| | | | | } |
| | | | | // – – – – – ---------- end FontsTable FTFontRenderer ---------- |
| | | | | // – – – – – FontsTable FTHeaderRenderer : render the column headers individually |
| | | | | /** Render column headings. |
| | | | | * Omit the "sorting arrow"; we always sort ascending |
| | | | | * Color the header background during the sort |
| | | | | */ |
| | | | | class FTHeaderRenderer extends DefaultTableCellRenderer { |
| | | | | @Override |
| | | | | public Component getTableCellRendererComponent( |
| | | | | JTable table, Object value, boolean isSelected, |
| | | | | boolean hasFocus, int row, int column) { |
| | | | | int modelCol = convertColumnIndexToModel(column); |
| | | | | setText(" " + (String)value); |
| | | | | setOpaque(true); |
| | | | | setFont(HEADER_FONT); |
| | | | | setBackground(nowSorting == modelCol |
| | | | | ? sortingHeaderBkgd : normalHeaderBkgd); |
| | | | | setForeground(Color.BLUE); |
| | | | | if (modelCol == styleColIndex) |
| | | | | setHorizontalAlignment(JLabel.CENTER); |
| | | | | return this; |
| | | | | } |
| | | | | /** the header looks better with a bottom line */ |
| | | | | @Override |
| | | | | protected void paintBorder(Graphics g) { |
| | | | | g.drawLine(0, getHeight() - 1, getWidth(), getHeight() - 1); |
| | | | | } |
| | | | | } |
| | | | | // – – – – – ---------- end FontsTable FTHeaderRenderer ---------- |
| | | | | // – – – – – FontsTable FTRowSorter : sort rows |
| | | | | /** |
| | | | | * Specialize sorting to patch over DefaultRowSorter bugs |
| | | | | * and to replace the usual sort behaviors. |
| | | | | */ |
| | | | | private class FTRowSorter |
| | | | | extends TableRowSorter<DefaultTableModel> { |
| | | | | // keep our own list of comparators |
| | | | | ArrayList<Comparator> comparators = new ArrayList<>(); |
| | | | | |
| | | | | /** |
| | | | | * Construct a row sorter. |
| | | | | * @param tmodel The TableModel being sorted (mainTable) |
| | | | | */ |
| | | | | FTRowSorter(DefaultTableModel tmodel) { |
| | | | | super(tmodel); |
| | | | | } |
| | | | | /** Get the comparator from our internal array */ |
| | | | | @Override |
| | | | | public Comparator<?> getComparator(int col) { |
| | | | | return comparators.get(col); |
| | | | | } |
| | | | | /** |
| | | | | * Remember a comparator. |
| | | | | * JAVA LIBRARY BUG: DefaultRowSorter (behavior is documented) |
| | | | | * discards prior comparators when a column is added |
| | | | | * @param index which comparator to set |
| | | | | * @param comp the new comparator |
| | | | | */ |
| | | | | @Override |
| | | | | public void setComparator(int index, Comparator<?>comp) { |
| | | | | while (index > comparators.size() - 1) |
| | | | | comparators.add(null); // (in practice, adds one element) |
| | | | | comparators.set(index, comp); |
| | | | | } |
| | | | | /** Never use toString for table row sorting. |
| | | | | * JAVA LIBRARY BUG: by not calling super.setComparator, |
| | | | | * DefaultRowSorter decides to use toString |
| | | | | * @param column Which column to reply for |
| | | | | * @return false |
| | | | | */ |
| | | | | @Override |
| | | | | protected boolean useToString(int column) { return false; } |
| | | | | /** |
| | | | | * Do a sort for a given set of keys. In this application |
| | | | | * each sort has only one key. |
| | | | | * <pre> |
| | | | | * Normal sort has two problems: |
| | | | | * a) if the table has been sorted on a column, |
| | | | | * reclicking the header does not sort again |
| | | | | * b) successive sorts are in opposite directions; |
| | | | | * this is not useful for fonts, so we sort always ascending |
| | | | | * This override of setSortKeys fixes both problems |
| | | | | * </pre> |
| | | | | * @param keys the list of keys to sort on. |
| | | | | */ |
| | | | | @Override |
| | | | | public void setSortKeys( |
| | | | | java.util.List<? extends SortKey> keys) { |
| | | | | finishEditing(); // save any in-progress category edit |
| | | | | super.setSortKeys(keys); |
| | | | | java.util.List<SortKey> copy |
| | | | | = new ArrayList<>(keys.size()); |
| | | | | for (SortKey k : keys) |
| | | | | copy.add(new SortKey(k.getColumn(), |
| | | | | SortOrder.ASCENDING)); |
| | | | | // during the sort, the column header changes color |
| | | | | // the column number in the sortkey is per the model |
| | | | | nowSorting = copy.get(0).getColumn(); |
| | | | | final JTableHeader tableHdr = FontsTable.this.getTableHeader(); |
| | | | | tableHdr.paintImmediately(0, 0, // change hdr color NOW |
| | | | | Integer.MAX_VALUE, Integer.MAX_VALUE); |
| | | | | |
| | | | | java.util.List<SortKey> newSortKeys |
| | | | | = Collections.unmodifiableList(copy); |
| | | | | if (getSortKeys().equals(newSortKeys)) |
| | | | | allRowsChanged(); // sort, the same way as before |
| | | | | else |
| | | | | super.setSortKeys(copy); // sort the new way |
| | | | | |
| | | | | SwingUtilities.invokeLater(()->{ |
| | | | | nowSorting = -1; // turn off header color |
| | | | | scrollTable.getVerticalScrollBar().setValue(0); // scroll to top |
| | | | | tableHdr.paintImmediately(0, 0, // revert header color |
| | | | | Integer.MAX_VALUE, Integer.MAX_VALUE); |
| | | | | }); |
| | | | | } // end setSortKeys |
| | | | | } // end FTPRowSorter |
| | | | | // – – – – – ---------- end FontsTable FTRowSorter ---------- |
| | | | | // – – – – ---------- end FontsTable ---------- |
| | | | | } // end FontsTable |
| | | | | // – – – ---------- end FontsTable class ---------- |
| | | | | // – – ---------- end FontViewer enclosed classes ---------- |
| | | | | // – – FontViewer - Menu : Creating menu items, saving categories, user interface |
| | | | | // – – – FontViewer createAction() : define actions for menus and keystrokes |
| | | | | /** |
| | | | | * Create an Action object for insertion in a menu or keymap. |
| | | | | * Sets the properties NAME, DISPLAYED_MNEMONIC_INDEX_KEY, |
| | | | | * SHORT_DESCRIPTION, ACCELERATOR_KEY. Obviates MNEMONIC_KEY. |
| | | | | * |
| | | | | * @param main JComponent whose action and key maps should be |
| | | | | * revised for accelerator keys |
| | | | | * @param name Name that should appear in a menu or button. |
| | | | | * If the name contains an underscore (_), the following character |
| | | | | * is identified as the mnemonic (usually underlined). The underline |
| | | | | * itself is deleted. |
| | | | | * @param tooltip |
| | | | | * If not null, the string is displayed as the tooltip for the menu item. |
| | | | | * @param accelerator If non-null, must be a String in the format |
| | | | | * defined by KeyStroke.getKeyStroke. |
| | | | | * Examples "ctrl S" (must be upper-case) |
| | | | | <pre> "alt typed A" "meta ctrl DELETE" </pre> |
| | | | | * @param tobedone An Actor typically created with a lambda expression: |
| | | | | <pre> Action twiddle = createAction("Do nothing", |
| | | | | * "waste computer cycles", |
| | | | | * null, () -> { twiddle(thumbs); }); </pre> |
| | | | | * @return An Action suitable for a button or menuitem. |
| | | | | * If additional properties are needed, |
| | | | | * assign the value to a temporary and add properties to it. |
| | | | | */ |
| | | | | public static Action createAction(JComponent main, String name, |
| | | | | String tooltip, String accelerator, |
| | | | | ActionListener tobedone) { |
| | | | | AbstractAction act = new AbstractAction() { |
| | | | | @Override |
| | | | | public void actionPerformed(ActionEvent e) { |
| | | | | tobedone.actionPerformed(e); |
| | | | | } |
| | | | | }; |
| | | | | int undX = name.indexOf('_'); |
| | | | | if (undX >= 0) { |
| | | | | name = name.replace("_",""); |
| | | | | act.putValue(Action.MNEMONIC_KEY, |
| | | | | KeyEvent.getExtendedKeyCodeForChar(name.charAt(undX))); |
| | | | | if (undX > 0) |
| | | | | act.putValue(Action.DISPLAYED_MNEMONIC_INDEX_KEY, undX); |
| | | | | } |
| | | | | act.putValue(Action.NAME, name); |
| | | | | if (tooltip != null) |
| | | | | act.putValue(Action.SHORT_DESCRIPTION, tooltip); |
| | | | | if (accelerator != null) { |
| | | | | KeyStroke ks = KeyStroke.getKeyStroke(accelerator); |
| | | | | act.putValue(Action.ACCELERATOR_KEY, ks); |
| | | | | } |
| | | | | return act; |
| | | | | } |
| | | | | |
| | | | | /** |
| | | | | * Create a menu item based on a given cut/copy/paste Action |
| | | | | * from DefaultEditorKit. |
| | | | | * @param act The Action to be initiated by this menu item. |
| | | | | * @param name The String to appear in the menu. |
| | | | | * @param mnemX Index of letter to be underlined in name on menu. |
| | | | | * @param acc The accelerator key. |
| | | | | * Displayed on menu and set in key bindings. |
| | | | | */ |
| | | | | static JMenuItem ccpMenuItem(Action act, |
| | | | | String name, int mnemX, String acc) { |
| | | | | JMenuItem item = new JMenuItem(act); |
| | | | | item.setText(name); |
| | | | | char ch = name.charAt(mnemX); |
| | | | | item.setMnemonic(KeyEvent.getExtendedKeyCodeForChar(ch)); |
| | | | | if (mnemX > 0) |
| | | | | item.setDisplayedMnemonicIndex(mnemX); |
| | | | | item.setAccelerator(KeyStroke.getKeyStroke(acc)); |
| | | | | return item; |
| | | | | } |
| | | | | // – – – ---------- end FontViewer createAction() ---------- |
| | | | | // – – – FontViewer insertMenuItems() : define menus |
| | | | | /** |
| | | | | * Add JMenus to a MenuBar and add FonViewer-specific Actions to each. |
| | | | | * Conceivably a client could add other items to the menubar. |
| | | | | * @param bar a JMenuBar to be filled in |
| | | | | */ |
| | | | | public void insertMenuItems(JMenuBar bar) { |
| | | | | // FILE menu |
| | | | | JMenu fileMenu = new JMenu("File" ); |
| | | | | fileMenu.setMnemonic(KeyEvent.VK_F); |
| | | | | bar.add(fileMenu); |
| | | | | // FILE->LOAD CATEGORIES |
| | | | | fileMenu.add(createAction(FontViewer.this, |
| | | | | "_Load categories ...", |
| | | | | "Prompt for the location of an " |
| | | | | + "additional categories file to load ",null, |
| | | | | e->askForCatSource() |
| | | | | )); |
| | | | | // FILE->SAVE CATEGORIES |
| | | | | fileMenu.add(saveAction=createAction(FontViewer.this, |
| | | | | "_Save categories", |
| | | | | (IS_IN_WEBSTART ? "Saving is disallowed under WebStart" |
| | | | | : "Save the category data (\"About\" on the Help menu " |
| | | | | + "shows where it will be saved)"), |
| | | | | "control pressed S", |
| | | | | e->{ |
| | | | | mainTable.finishEditing(); |
| | | | | catsMap.save(); |
| | | | | })); |
| | | | | if (saveAction == null) {} |
| | | | | else if (IS_IN_WEBSTART) saveAction.setEnabled(false); |
| | | | | else saveAction.setEnabled(catsMap.getMapDest() != null); |
| | | | | // FILE->SAVE AS |
| | | | | fileMenu.add(saveasAction=createAction(FontViewer.this, |
| | | | | "Save _as ...", |
| | | | | IS_IN_WEBSTART ? "Saving is disallowed under WebStart" |
| | | | | : "Prompt for a location " |
| | | | | + "and begin saving the category data there", |
| | | | | null, |
| | | | | e->{ |
| | | | | mainTable.finishEditing(); |
| | | | | JButton help = new JButton("Help"); |
| | | | | help.addActionListener(butev->browseGuide("categoriesfile")); |
| | | | | Object[] buttons = new Object[]{"Save", "Don't save", help}; |
| | | | | JTextField dest = new JTextField(DEFAULT_CATSMAP, 40); |
| | | | | int opt = talkToUser("Save Category Data To", |
| | | | | "Where would you like to store category data?", |
| | | | | dest, buttons); |
| | | | | if (opt == 0) { |
| | | | | String errmsg = setSaveFile(dest.getText()); |
| | | | | if (errmsg != null) |
| | | | | tellUser("File save failed", errmsg); |
| | | | | } |
| | | | | })); |
| | | | | if (IS_IN_WEBSTART) { |
| | | | | String msg = "You can modify the first two table columns;" |
| | | | | + "<br>but FontViewer is currently running under" |
| | | | | + "<br><b>Java WebStart</b>, " |
| | | | | + "so your changes cannot be saved."; |
| | | | | tellUser("Not saving category data", msg); |
| | | | | saveasAction.setEnabled(false); |
| | | | | } |
| | | | | // FILE->EXIT |
| | | | | fileMenu.add(createAction(FontViewer.this, "E_xit", |
| | | | | "Make sure categories are saved, and close window", |
| | | | | null, |
| | | | | e->{ |
| | | | | final Container top = getTopLevelAncestor(); |
| | | | | if (top instanceof Window) |
| | | | | // simulate clicking the red X (send a WindowClosing event) |
| | | | | TOOLKIT.getSystemEventQueue() |
| | | | | .postEvent(new WindowEvent((Window) top, |
| | | | | WindowEvent.WINDOW_CLOSING)); |
| | | | | } |
| | | | | )); |
| | | | | |
| | | | | // EDIT menu |
| | | | | JMenu editMenu = new JMenu("Edit"); |
| | | | | editMenu.setMnemonic(KeyEvent.VK_E); |
| | | | | bar.add(editMenu); |
| | | | | editMenu.add(ccpMenuItem(new DefaultEditorKit.CutAction(), |
| | | | | "Cut", 2, "control X")); |
| | | | | editMenu.add(ccpMenuItem(new DefaultEditorKit.CopyAction(), |
| | | | | "Copy", 0, "control C")); |
| | | | | editMenu.add(ccpMenuItem(new DefaultEditorKit.PasteAction(), |
| | | | | "Paste", 0, "control V")); |
| | | | | editMenu.add(sampleEditor.resetAction); |
| | | | | |
| | | | | // HELP menu |
| | | | | JMenu helpMenu = new JMenu("Help"); |
| | | | | helpMenu.setMnemonic(KeyEvent.VK_H); |
| | | | | bar.add(helpMenu); |
| | | | | // HELP->USING FONTVIEWER |
| | | | | helpMenu.add(createAction(FontViewer.this, |
| | | | | "_Using FontViewer", |
| | | | | "Browse the instructions for using FontViewer", |
| | | | | "F1", |
| | | | | e->browseGuide(null) |
| | | | | )); |
| | | | | // HELP->ABOUT FONTVIEWER |
| | | | | helpMenu.add(createAction(FontViewer.this, |
| | | | | "_About FontViewer", |
| | | | | "FontViewer version and info on categories file", |
| | | | | null, |
| | | | | e->about() |
| | | | | )); |
| | | | | } |
| | | | | // – – – ---------- end FontViewer insertMenuItems() ---------- |
| | | | | // – – – FontViewer loadCat() : Load categories from various places |
| | | | | /** |
| | | | | * Read categories |
| | | | | * and, if successfully read from a local file, |
| | | | | * add the source name to the categorySources array. |
| | | | | * @param name String naming a source for category data. |
| | | | | * @return True if the read succeeded. |
| | | | | */ |
| | | | | private boolean loadCat(String name) { |
| | | | | if ( ! catsMap.read(this.getClass(), name)) |
| | | | | return false; |
| | | | | categorySources.add(name); |
| | | | | return true; |
| | | | | } |
| | | | | /** |
| | | | | * Load categories data from a few standard location. |
| | | | | * The ordering gets the latest data from the website or, if it isn't |
| | | | | * available, the jar installation directory. And then the user's |
| | | | | * additions are read, overriding any previous values. |
| | | | | * The locations expand to |
| | | | | * jardir:/fontcategories.txt |
| | | | | * http://physpics.com/Java/apps/fontviewer/fontcategories.txt |
| | | | | * resource:/fontcategories.txt (top-level of jar file) |
| | | | | * userhome:/FontViewer/fontcategories.txt (default save location) |
| | | | | */ |
| | | | | public void loadInitialCategories() { |
| | | | | loadCategories(new String[]{ |
| | | | | "jardir:" + CATS_FILE, |
| | | | | SITE_URL + "/" + CATS_FILE, |
| | | | | "resource:/"+CATS_FILE, |
| | | | | DEFAULT_CATSMAP |
| | | | | }); |
| | | | | } |
| | | | | /** Load categories from a list of specified locations. |
| | | | | * Set categoriesSourceFile to the latest source read. Nominally, |
| | | | | * the last location read from should be the user's own categories, |
| | | | | * overriding the more general categories data from other sources. |
| | | | | * @param srcName a String array of categories sources. |
| | | | | * @return true iff at least one source was read successfully |
| | | | | */ |
| | | | | public boolean loadCategories(String[] srcName) { |
| | | | | int origSrcCnt = categorySources.size(); |
| | | | | for (String fnm : srcName) |
| | | | | loadCat(fnm); |
| | | | | if (origSrcCnt < categorySources.size()) { |
| | | | | mainTable.replaceCategories(); |
| | | | | return true; |
| | | | | } |
| | | | | return false; |
| | | | | } |
| | | | | /** Prompt for a location and load categories data from it. */ |
| | | | | public void askForCatSource() { |
| | | | | if (IS_IN_WEBSTART) |
| | | | | return; |
| | | | | JButton help = new JButton("Help"); |
| | | | | help.addActionListener(butev->browseGuide("categoriesfile")); |
| | | | | Object[] buttons = new Object[]{"Load", "Cancel", help}; |
| | | | | JTextField dest = new JTextField(DEFAULT_CATSMAP, 40); |
| | | | | int opt = talkToUser("Load Categories Datsa From", |
| | | | | "Choose a location for reading categories data", |
| | | | | dest, buttons); |
| | | | | if (opt != 0) return; |
| | | | | if (loadCat(dest.getText())) |
| | | | | mainTable.replaceCategories(); |
| | | | | else |
| | | | | tellUser("Load failed", |
| | | | | "Could not read category data from<br> " |
| | | | | + "<code>" + dest.getText() + "</code>"); |
| | | | | } |
| | | | | // – – – ---------- end FontViewer loadCat() ---------- |
| | | | | // – – – FontViewer setSaveFile() : Establish category save file |
| | | | | /** |
| | | | | * Set the catsMap save file and do an initial save to it. |
| | | | | * @param fnm File name of file to save into. |
| | | | | * May be null or "" to turn off saving |
| | | | | * @return null if changed the save file and saved the data; |
| | | | | * otherwise return an error message. |
| | | | | * If webstart, always an error. |
| | | | | */ |
| | | | | public String setSaveFile(String fnm) { |
| | | | | if (IS_IN_WEBSTART) |
| | | | | return "The program cannot save category data " |
| | | | | + "when running under webstart"; |
| | | | | StringMap.Dest val = catsMap.setMapDest(this.getClass(), fnm); |
| | | | | if (val != StringMap.Dest.FAILED) { |
| | | | | if (saveAction != null) |
| | | | | saveAction.setEnabled(catsMap.getMapDest() != null); |
| | | | | catsMap.save(); // do initial save |
| | | | | if (catsMap.flush()) // check that no changes are pending |
| | | | | return null; |
| | | | | } |
| | | | | // == Dest.FAILED |
| | | | | if (saveAction != null) |
| | | | | saveAction.setEnabled(false); |
| | | | | String msg = "FontViewer could not save category data in<br>" |
| | | | | + "<code> " |
| | | | | + (fnm==null||fnm.isEmpty() ? "(not saving data)" : fnm) |
| | | | | + "</code><br>" |
| | | | | + "To save data, click the " |
| | | | | + "<b>Save as ...</b> option in the <b>File</b> menu."; |
| | | | | return msg; |
| | | | | } |
| | | | | // – – – ---------- end FontViewer setSaveFile() ---------- |
| | | | | // – – – FontViewer help options : usage instructions and the "About" text |
| | | | | /** Browse FVHelp.html from with the user's default browser. |
| | | | | * @param anchor null, or the name of an anchor in HELP_URL (no '#') |
| | | | | */ |
| | | | | public static void browseGuide(String anchor) { |
| | | | | String url = HELP_URL + (anchor == null ? "" : "#" + anchor); |
| | | | | Desktop dt; |
| | | | | if (Desktop.isDesktopSupported() && (dt=Desktop.getDesktop()) |
| | | | | .isSupported(Desktop.Action.BROWSE)) { |
| | | | | try { |
| | | | | dt.browse(new java.net.URI(url)); |
| | | | | return; |
| | | | | } |
| | | | | catch (java.io.IOException | java.net.URISyntaxException ex) { |
| | | | | // fall thru |
| | | | | } |
| | | | | } |
| | | | | String msg = "Please browse <a href='"+url+"'>" |
| | | | | +"FontViewer Help</a>"; |
| | | | | String msg = "Please browse <a href='"+url+"'>" |
| | | | | +"FontViewer Help</a>" + fillCutBuffer(url); |
| | | | | JLabel lblmsg = new JLabel("<html>" + msg + "</html>"); |
| | | | | JOptionPane.showMessageDialog(null, lblmsg); |
| | | | | tellUser("FontViewer Help", msg); |
| | | | | } |
| | | | | /** |
| | | | | * Show a message with the version of FontViewer |
| | | | | * and which font categories files are in play. |
| | | | | */ |
| | | | | public void about() { |
| | | | | // Get version string from MANIFEST.MF in the jar file |
| | | | | String version = getClass().getPackage().getImplementationVersion(); |
| | | | | String msg = "<a href='"+SITE_URL+"/'>" |
| | | | | + "FontViewer</a> version " + version |
| | | | | + " by ZweiBieren@PhysPics.com"; |
| | | | | msg += fillCutBuffer(SITE_URL + "/"); |
| | | | | msg += "<br>Categories have been read from these sources:"; |
| | | | | for (String src : categorySources) |
| | | | | msg += "<br> <code>"+src+"</code>"; |
| | | | | if (IS_IN_WEBSTART) msg += "<br>Changes are not being saved " |
| | | | | + "because we are running via Webstart"; |
| | | | | else { |
| | | | | File dest = catsMap.getMapDest(); |
| | | | | msg += (dest != null) |
| | | | | ? "<br>Now saving to <br> <code>" |
| | | | | + dest.getAbsolutePath() + "</code>" |
| | | | | : "<br>NOT SAVING CATEGORY DATA." |
| | | | | + "<br>You may want to choose \"Save as ...\"" |
| | | | | + " from the File menu"; |
| | | | | } |
| | | | | JLabel lblmsg = new JLabel("<html>" + msg + "</html>"); |
| | | | | JOptionPane.showMessageDialog(this, lblmsg); |
| | | | | tellUser("About FontViewer", msg); |
| | | | | } |
| | | | | // – – – ---------- end FontViewer help options ---------- |
| | | | | // – – – FontViewer tellUser() : Dialog boxes and the cut buffer |
| | | | | /** Put new content in the cut buffer. |
| | | | | * @param cutee The String to put in the cut buffer. |
| | | | | * @return If cutbuffer is set, return a message about it. |
| | | | | * Otherwise, return an empty String. |
| | | | | */ |
| | | | | static String fillCutBuffer(String cutee) { |
| | | | | if (IS_IN_WEBSTART) return "Cut buffer is unavailable under WebStart"; |
| | | | | try { |
| | | | | java.awt.datatransfer.StringSelection stringSelection |
| | | | | = new java.awt.datatransfer.StringSelection(cutee); |
| | | | | CLIPBOARD.setContents(stringSelection, null); |
| | | | | return "<br><font size='-1'>" |
| | | | | + "(The link value is now in the cut buffer." |
| | | | | + " You may paste it into your browesr.)</font>"; |
| | | | | } catch (java.awt.HeadlessException |
| | | | | | java.security.AccessControlException ex) { |
| | | | | return ""; |
| | | | | } |
| | | | | } |
| | | | | /** |
| | | | | * Display a message in a dialog box. |
| | | | | * The only user option is "OK" to dismiss the message. |
| | | | | * @param title To appear in the title bar |
| | | | | * @param message See askUser. |
| | | | | */ |
| | | | | public static void tellUser(String title, String message) { |
| | | | | if (message != null) |
| | | | | talkToUser(title, message, null, new String[]{"OK"}); |
| | | | | } |
| | | | | /** |
| | | | | * Popup a query box where a user fills in data and clicks a button. |
| | | | | * If the body argument is a JTextComponent, a listener is added |
| | | | | * so typing return will click the first response button. |
| | | | | * |
| | | | | * @param title String to appear in popup's title bar |
| | | | | * @param message null or a message to display. |
| | | | | * It will be wrapped in a JLabel and <html> tags, |
| | | | | * including a <font> tag for a larger size. |
| | | | | * @param body The contents or null. A JTextComponent |
| | | | | * is processed as described above. A JComponent is displayed. |
| | | | | * Other objects have their to String value displayed. |
| | | | | * @param options An array of Strings |
| | | | | * to be displayed in buttons beneath the body |
| | | | | * @return The index of the selected button. Or -1 if for window dismissal. |
| | | | | */ |
| | | | | public static int talkToUser(String title, |
| | | | | String message, Object body, Object[] options) { |
| | | | | Object q = (message == null) ? null |
| | | | | : new JLabel("<html><font size='+1'>"+message+"</font></html>"); |
| | | | | JOptionPane jop = new JOptionPane(new Object[]{q,body}, |
| | | | | JOptionPane.PLAIN_MESSAGE, JOptionPane.YES_NO_OPTION, |
| | | | | null, options, options[0]); |
| | | | | ICON64, options, options[0]); |
| | | | | JDialog dia = jop.createDialog(title); |
| | | | | dia.setAlwaysOnTop(true); |
| | | | | dia.setVisible(true); // (this returns only when user is done) |
| | | | | dia.dispose(); |
| | | | | Object val = jop.getValue(); |
| | | | | if (val != null) |
| | | | | for (int i = 0; i < options.length; i++) |
| | | | | if (val.equals(options[i])) return i; |
| | | | | return -1; |
| | | | | } |
| | | | | // – – – ---------- end FontViewer tellUser() ---------- |
| | | | | // – – ---------- end FontViewer - Menu ---------- |
| | | | | // – – FontViewer main() : the main program if FontViewer is not part of a larger application |
| | | | | /////////////////////////////////////// |
| | | | | // main() |
| | | | | |
| | | | | /** Run this application |
| | | | | * @param args If given, each arg is the location of categories data. |
| | | | | */ |
| | | | | public static void main(String[] args) { |
| | | | | FontViewer.printFontList(System.out); |
| | | | | // because they are basic documentation, |
| | | | | // tooltips need faster startup and longer duration |
| | | | | ToolTipManager tipman = ToolTipManager.sharedInstance(); |
| | | | | tipman.setInitialDelay(375); // default is 750 |
| | | | | tipman.setDismissDelay(6000); // default is 4000 |
| | | | | // because askUser text is size='+1', buttons need to be bigger |
| | | | | // see http://stackoverflow.com/questions/4017042 |
| | | | | UIManager.put("OptionPane.buttonFont", |
| | | | | new javax.swing.plaf.FontUIResource(FontViewer.BUTTON_FONT)); |
| | | | | // If TAB to a button, ENTER should choose it |
| | | | | // https://stackoverflow.com/a/16923982/1614775 |
| | | | | UIManager.put("Button.defaultButtonFollowsFocus", Boolean.TRUE); |
| | | | | |
| | | | | // create a FontViewer as a standalone app |
| | | | | final FontViewer viewer = FontViewer.create(); |
| | | | | if (args.length != 0) |
| | | | | viewer.loadCategories(args); |
| | | | | else |
| | | | | viewer.loadInitialCategories(); |
| | | | | viewer.setSaveFile(DEFAULT_CATSMAP); |
| | | | | tellUser("Setting initial categories save file", |
| | | | | viewer.setSaveFile(DEFAULT_CATSMAP)); |
| | | | | |
| | | | | SwingUtilities.invokeLater(()->{ |
| | | | | JFrame frame = viewer.wrapWithJFrame(); |
| | | | | frame.setBounds(40, 25, 725, 800); |
| | | | | frame.pack(); |
| | | | | frame.setVisible(true); |
| | | | | }); |
| | | | | } // end method main() |
| | | | | // – – ---------- end FontViewer main() ---------- |
| | | | | } // end class FontViewer |