Textured Tool Bar Sample Code

This sample program simulates the appearance of the tool bars in the Mail and TextEdit applications.


import java.awt.*;
import java.awt.geom.RoundRectangle2D;
import javax.swing.*;

import org.jetbrains.annotations.*;

/**
 * Sample program for textured tool bars. The application specific icons are not included!
 */
public class ToolbarSampleProgram
{
  public final static int POSITION_FIRST = 0;
  public final static int POSITION_MIDDLE = 1;
  public final static int POSITION_LAST = 2;
  public final static int POSITION_ONLY = 3;

  protected final Icon defaultIcon = createDefaultIcon();

  public ToolbarSampleProgram()
  {
    {
      MailFrame fr = new MailFrame();
      fr.setBounds(50, 100, 800, 600);
      fr.setVisible(true);
    }

    {
      TextEditFrame fr = new TextEditFrame();
      fr.setBounds(900, 200, 600, 600);
      fr.setVisible(true);
    }
  }

  protected class SampleToolbarFrame
    extends JFrame
  {
    public SampleToolbarFrame(@NotNull JToolBar toolBar)
    {
      JRootPane rootPane = getRootPane();
      rootPane.putClientProperty("Aqua.windowStyle", "unifiedToolBar");
      setLayout(new BorderLayout());
      add(toolBar, BorderLayout.NORTH);
    }
  }

  protected class MailFrame
    extends SampleToolbarFrame
  {
    private final JPanel p;

    public MailFrame()
    {
      super(new MailToolbar("regular"));
      p = new JPanel();
      p.setFocusable(true);
      add(p);
    }

    @Override
    public void addNotify()
    {
      super.addNotify();
      p.requestFocusInWindow();
    }
  }

  protected class TextEditFrame
    extends SampleToolbarFrame
  {
    private final JTextPane p;

    public TextEditFrame()
    {
      super(new TextEditToolbar("mini"));
      p = new JTextPane();
      add(p);
    }

    @Override
    public void addNotify()
    {
      super.addNotify();
      p.requestFocusInWindow();
    }
  }

  protected class MailToolbar
    extends JToolBar
  {
    public MailToolbar(@NotNull String sizeVariant)
    {
      add(new MyMailRow(sizeVariant));
    }
  }

  protected class TextEditToolbar
    extends JToolBar
  {
    public TextEditToolbar(@NotNull String sizeVariant)
    {
      add(new MyTextEditRow(sizeVariant));
    }
  }

  protected class RowPaneBase
    extends JPanel
  {
    protected GroupLayout.SequentialGroup hGroup;
    protected GroupLayout.ParallelGroup vGroup;

    protected RowPaneBase()
    {
      setOpaque(false);
      GroupLayout layout = new GroupLayout(this);
      setLayout(layout);
      hGroup = layout.createSequentialGroup();
      vGroup = layout.createParallelGroup();
      layout.setHorizontalGroup(hGroup);
      layout.setVerticalGroup(vGroup);
    }

    protected void add(@NotNull JComponent c)
    {
      hGroup.addComponent(c, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE);
      vGroup.addComponent(c);
    }

    protected void add(@NotNull JComponent c, int width)
    {
      hGroup.addComponent(c, width, width, width);
      vGroup.addComponent(c);
    }

    protected void addExpandingGap()
    {
      hGroup.addGap(0, 10, 10000);
      vGroup.addGap(0);
    }

    protected void addGap(int width)
    {
      hGroup.addGap(width, width, width);
      vGroup.addGap(0);
    }
  }

  protected abstract class RowBase
    extends RowPaneBase
  {
    protected final @NotNull String sizeVariant;
    private final int buttonWidth = 41;
    private final int iconSize;
    private final float sizeFudge;

    protected RowBase(@NotNull String sizeVariant)
    {
      this.sizeVariant = sizeVariant;
      iconSize = determineIconSize();
      sizeFudge = determineSizeFudge();
    }

    protected int determineIconSize()
    {
      if (sizeVariant.equals("small")) {
        return 13;
      } else if (sizeVariant.equals("mini")) {
        return 11;
      } else {
        return 15;
      }
    }

    protected float determineSizeFudge()
    {
      // assumes sizes are based on "regular"
      if (sizeVariant.equals("small")) {
        return 0.85f;
      } else if (sizeVariant.equals("mini")) {
        return 0.75f;
      } else {
        return 1;
      }
    }

    @Override
    protected void add(@NotNull JComponent c, int width)
    {
      width = (int) (width * sizeFudge);
      c.setMinimumSize(new Dimension(width, 0));
      c.setMaximumSize(new Dimension(width, 1000));
      super.add(c, width);
    }

    protected AbstractButton createPushButton(@NotNull Icon icon)
    {
      return createSegmentedButton(icon, POSITION_ONLY);
    }

    protected AbstractButton createPushButton(@NotNull String label)
    {
      return createSegmentedButton(label, POSITION_ONLY);
    }

    protected AbstractButton createToggleButton(@NotNull Icon icon)
    {
      return createSegmentedToggleButton(icon, POSITION_ONLY);
    }

    protected AbstractButton createToggleButton(@NotNull String label)
    {
      return createSegmentedToggleButton(label, POSITION_ONLY);
    }

    protected AbstractButton createSegmentedButton(@NotNull Icon icon, int position)
    {
      JButton b = new JButton(icon);
      b.putClientProperty("JButton.buttonType", "segmented");
      b.putClientProperty("JButton.segmentPosition", toPosition(position));
      return setup(b);
    }

    protected AbstractButton createSegmentedButton(@NotNull String label, int position)
    {
      JButton b = new JButton(label);
      b.putClientProperty("JButton.buttonType", "segmented");
      b.putClientProperty("JButton.segmentPosition", toPosition(position));
      return setup(b);
    }

    protected AbstractButton createSegmentedToggleButton(@NotNull Icon icon, int position)
    {
      JToggleButton b = new JToggleButton(icon);
      b.putClientProperty("JButton.buttonType", "segmented");
      b.putClientProperty("JButton.segmentPosition", toPosition(position));
      return setup(b);
    }

    protected AbstractButton createSegmentedToggleButton(@NotNull String label, int position)
    {
      JToggleButton b = new JToggleButton(label);
      b.putClientProperty("JButton.buttonType", "segmented");
      b.putClientProperty("JButton.segmentPosition", toPosition(position));
      return setup(b);
    }

    protected AbstractButton createSegmentedColorButton(@NotNull String label, int position, @Nullable Color color)
    {
      JButton b = new ColorButton(label, color);
      b.putClientProperty("JButton.buttonType", "segmented");
      b.putClientProperty("JButton.segmentPosition", toPosition(position));
      return setup(b);
    }

    protected AbstractButton createSeparatedButton(@NotNull Icon icon, int position)
    {
      JButton b = new JButton(icon);
      b.putClientProperty("JButton.buttonType", "segmentedSeparated");
      b.putClientProperty("JButton.segmentPosition", toPosition(position));
      return setup(b);
    }

    protected JComboBox createPullDownButton(@NotNull String label)
    {
      JComboBox b = new JComboBox();
      b.setEditable(false);
      b.putClientProperty("JComponent.sizeVariant", sizeVariant);
      b.putClientProperty("JComboBox.isPopDown", true);
      b.putClientProperty("JComboBox.title", label);
      return b;
    }

    protected JComboBox createPullDownButton(@NotNull Icon icon)
    {
      JComboBox b = new JComboBox();
      b.setEditable(false);
      b.putClientProperty("JComponent.sizeVariant", sizeVariant);
      b.putClientProperty("JComboBox.isPopDown", true);
      b.putClientProperty("JComboBox.title", icon);
      return b;
    }

    protected JComboBox createPopUpButton()
    {
      JComboBox b = new JComboBox();
      b.setEditable(false);
      b.putClientProperty("JComponent.sizeVariant", sizeVariant);
      return b;
    }

    protected JComboBox createComboBox()
    {
      JComboBox b = new JComboBox();
      b.setEditable(true);
      b.putClientProperty("JComponent.sizeVariant", sizeVariant);
      return b;
    }

    protected JTextField createTextField()
    {
      JTextField t = new JTextField();
      t.putClientProperty("JComponent.sizeVariant", sizeVariant);
      return t;
    }

    protected JTextField createSearchField()
    {
      JTextField t = new JTextField();
      t.putClientProperty("JComponent.sizeVariant", sizeVariant);
      t.putClientProperty("JTextField.style", "search");
      return t;
    }

    protected AbstractButton setup(@NotNull AbstractButton b)
    {
      b.putClientProperty("JComponent.sizeVariant", sizeVariant);
      if (!b.isMinimumSizeSet()) {
        b.setMinimumSize(new Dimension(buttonWidth, 0));
      }
      return b;
    }

    protected @NotNull Icon getIcon(@NotNull String name)
    {
      return adjustIcon(defaultIcon);
    }

    protected @NotNull Icon adjustIcon(@NotNull Icon ic)
    {
      if (ic instanceof ImageIcon) {
        ImageIcon ii = (ImageIcon) ic;
        double sx = (1.3 * iconSize * sizeFudge) / ic.getIconWidth();
        double sy = (1.0 * iconSize * sizeFudge) / ic.getIconHeight();
        double s = Math.min(sx, sy);
        int width = (int) (ic.getIconWidth() * s);
        int height = (int) (ic.getIconHeight() * s);
        return new ImageIcon(ii.getImage().getScaledInstance(width, height, 0));
      } else {
        return ic;
      }
    }
  }

  /**
    A row of components that simulates the contents of the Mail toolbar. Mail uses regular size.
  */

  protected class MyMailRow
    extends RowBase
  {
    private AbstractButton getMailButton;
    private AbstractButton composeButton;
    private AbstractButton archiveButton;
    private AbstractButton trashButton;
    private AbstractButton junkButton;
    private AbstractButton replyButton;
    private AbstractButton replyAllButton;
    private AbstractButton forwardButton;
    private AbstractButton flagButton;
    private AbstractButton flagMenuButton;
    private JTextField searchField;

    public MyMailRow(@NotNull String sizeVariant)
    {
      super(sizeVariant);

      getMailButton = createPushButton(getIcon("GetMailTemplate"));
      composeButton = createPushButton(getIcon("ComposeTemplate"));
      archiveButton = createPushButton(getIcon("ArchiveTemplate"));
      trashButton = createSegmentedButton(getIcon("TrashTemplate"), POSITION_FIRST);
      junkButton = createSegmentedButton(getIcon("JunkTemplate"), POSITION_LAST);
      replyButton = createSegmentedButton(getIcon("ReplyTemplate"), POSITION_FIRST);
      replyAllButton = createSegmentedButton(getIcon("ReplyAllTemplate"), POSITION_MIDDLE);
      forwardButton = createSegmentedButton(getIcon("ForwardTemplate"), POSITION_LAST);
      flagButton = createSegmentedButton(getIcon("Flag"), POSITION_FIRST);
      flagMenuButton = createSegmentedButton(getIcon("DownMenuTemplate"), POSITION_LAST);
      searchField = createSearchField();

      addGap(8);
      add(getMailButton, 38);
      addGap(8);
      add(composeButton, 38);
      addGap(82);
      add(archiveButton, 38);
      addGap(8);
      add(trashButton, 36);
      add(junkButton, 36);
      addGap(8);
      add(replyButton, 36);
      add(replyAllButton, 35);
      add(forwardButton, 36);
      addGap(8);
      add(flagButton, 27);
      add(flagMenuButton, 15);
      addExpandingGap();
      add(searchField, 325);
      addGap(8);
    }
  }

  /**
    A row of components that simulates the contents of the TextEdit toolbar. TextEdit uses mini size.
  */

  protected class MyTextEditRow
    extends RowBase
  {
    private JComboBox paragraphStyleMenuButton;
    private JComboBox fontFamilyMenuButton;
    private JComboBox typefaceMenuButton;
    private JComboBox fontSizeSelector;
    private AbstractButton textColorButton;
    private AbstractButton textBackgroundColorButton;
    private AbstractButton boldButton;
    private AbstractButton italicButton;
    private AbstractButton underlineButton;
    private AbstractButton leftAlignButton;
    private AbstractButton centerAlignButton;
    private AbstractButton rightAlignButton;
    private AbstractButton justifyButton;
    private JComboBox spacingMenuButton;
    private JComboBox listFormatMenuButton;

    public MyTextEditRow(@NotNull String sizeVariant)
    {
      super(sizeVariant);

      paragraphStyleMenuButton = createPullDownButton("\u00B6");
      fontFamilyMenuButton = createPopUpButton();
      typefaceMenuButton = createPopUpButton();
      fontSizeSelector = createComboBox();
      textColorButton = createSegmentedColorButton("", POSITION_ONLY, Color.BLACK);
      textBackgroundColorButton = createSegmentedColorButton("a", POSITION_ONLY, null);
      boldButton = createSegmentedToggleButton("B", POSITION_FIRST);
      italicButton = createSegmentedToggleButton("I", POSITION_MIDDLE);
      underlineButton = createSegmentedToggleButton("U", POSITION_LAST);
      leftAlignButton = createSegmentedToggleButton(getIcon("LeftAlignTemplate"), POSITION_FIRST);
      centerAlignButton = createSegmentedToggleButton(getIcon("CenterTemplate"), POSITION_MIDDLE);
      rightAlignButton = createSegmentedToggleButton(getIcon("RightAlignTemplate"), POSITION_MIDDLE);
      justifyButton = createSegmentedToggleButton(getIcon("JustifyTemplate"), POSITION_LAST);
      spacingMenuButton = createPopUpButton();
      listFormatMenuButton = createPullDownButton(getIcon("ListFormatTemplate"));

      paragraphStyleMenuButton.setModel(createParagraphStyleModel());
      fontFamilyMenuButton.setModel(createFontFamilyModel());
      typefaceMenuButton.setModel(createTypeFaceModel());
      fontSizeSelector.setModel(createFontSizeModel());
      spacingMenuButton.setModel(createSpacingModel());
      listFormatMenuButton.setModel(createListFormatModel());

      boldButton.setSelected(true);

      {
        Font f = textBackgroundColorButton.getFont();
        textBackgroundColorButton.setFont(f.deriveFont(f.getSize2D()+4).deriveFont(Font.BOLD));
      }

      {
        int size = getTypeSize();
        boldButton.setFont(new Font("Baskerville", Font.BOLD, size));
        italicButton.setFont(new Font("Baskerville", Font.BOLD + Font.ITALIC, size));
        underlineButton.setFont(new Font("Baskerville", Font.PLAIN, size));
      }

      {
        Color g = new Color(115, 115, 115);
        boldButton.setForeground(g);
        italicButton.setForeground(g);
        underlineButton.setForeground(g);
        italicButton.setEnabled(false);
      }

      {
        ButtonGroup g = new ButtonGroup();
        g.add(leftAlignButton);
        g.add(centerAlignButton);
        g.add(rightAlignButton);
        g.add(justifyButton);
        leftAlignButton.setSelected(true);
      }

      add(paragraphStyleMenuButton, 26);
      addGap(6);
      add(fontFamilyMenuButton, 99);
      addGap(2);
      add(typefaceMenuButton, 79);
      addGap(2);
      add(fontSizeSelector, 45);
      addGap(6);
      add(textColorButton, 23);
      addGap(2);
      add(textBackgroundColorButton, 23);
      addGap(6);
      add(boldButton, 18);
      add(italicButton, 18);
      add(underlineButton, 18);
      addGap(6);
      add(leftAlignButton, 18);
      add(centerAlignButton, 18);
      add(rightAlignButton, 18);
      add(justifyButton, 18);
      addGap(6);
      add(spacingMenuButton, 38);
      addGap(2);
      add(listFormatMenuButton, 38);
    }

    protected int getTypeSize()
    {
      if (sizeVariant.equals("small")) {
        return 15;
      } else if (sizeVariant.equals("regular")) {
        return 18;
      } else {
        return 13;
      }
    }

    @Override
    protected float determineSizeFudge()
    {
      // sizes are based on "mini"
      if (sizeVariant.equals("small")) {
        return 1.15f;
      } else if (sizeVariant.equals("regular")) {
        return 1.35f;
      } else {
        return 1;
      }
    }
  }

  private ComboBoxModel createParagraphStyleModel()
  {
    String[] items = { "Default", "Header" };
    return new DefaultComboBoxModel<>(items);
  }

  private ComboBoxModel createFontFamilyModel()
  {
    String[] items = { "Lucida Grande", "Helvetica" };
    return new DefaultComboBoxModel<>(items);
  }

  private ComboBoxModel createTypeFaceModel()
  {
    String[] items = { "Regular", "Bold" };
    return new DefaultComboBoxModel<>(items);
  }

  private ComboBoxModel createFontSizeModel()
  {
    String[] items = { "9", "10", "11", "12", "13", "14", "18", "24", "36", "48", "64" };
    return new DefaultComboBoxModel<>(items);
  }

  private ComboBoxModel createSpacingModel()
  {
    String[] items = { "0.5", "0.6", "0.7", "0.8", "0.9", "1.0", "1.1", "1.2", "1.3", "1.4", "1.5" };
    ComboBoxModel model = new DefaultComboBoxModel<>(items);
    model.setSelectedItem("1.0");
    return model;
  }

  private ComboBoxModel createListFormatModel()
  {
    String[] items = { "None", "1. 2. 3..." };
    return new DefaultComboBoxModel<>(items);
  }

  private class ColorButton
    extends JButton
  {
    private @Nullable Color color;

    public ColorButton(@NotNull String label, @Nullable Color color)
    {
      super(label);
      this.color = color;
    }

    @Override
    public void paintComponent(Graphics g)
    {
      super.paintComponent(g);

      Graphics2D gg = (Graphics2D) g;
      gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

      int x = 3;
      int y = 3;
      int right = getWidth() - 6;
      int bottom = getHeight() - 8;

      Shape s = new RoundRectangle2D.Double(x, y, right, bottom, 4, 4);

      if (color == null) {
        gg.setColor(Color.RED);
        gg.drawLine(x+1, bottom+1, right+1, y+1);

      } else {
        gg.setColor(color);
        gg.fill(s);
      }

      gg.setStroke(new BasicStroke(1));
      gg.setColor(new Color(232, 232, 232));
      gg.draw(s);
    }
  }

  private @NotNull String toPosition(int position)
  {
    switch (position) {
      case POSITION_FIRST:
        return "first";
      case POSITION_MIDDLE:
        return "middle";
      case POSITION_LAST:
        return "last";
      default:
        return "only";
    }
  }

  private Icon createDefaultIcon() {
    String imageName = "NSImage://NSActionTemplate";
    Image im = Toolkit.getDefaultToolkit().getImage(imageName);
    if (im != null) {
      return new ImageIcon(im);
    }
    System.err.println("Unable to load image: " + imageName);
    return null;
  }
}