@Override
 public void onExit() {
   // save all painters
   for (EntryListPanel panel : listPanelSet) {
     myjava.gui.syntax.Painter painter = panel.getPainter();
     if (!painter.equals(myjava.gui.syntax.Painter.getDefaultInstance())) {
       myjava.gui.syntax.Painter.add(painter);
       setConfig("painter.userDefined." + painter.getName(), painter.toWritableString());
     }
     if (painter.equals(painterComboBox.getSelectedItem())) {
       // selected painter
       setConfig("syntax.selectedPainter", painter.getName());
       myjava.gui.syntax.Painter.setCurrentInstance(painter);
     }
   }
   // remove "removed painters"
   for (myjava.gui.syntax.Painter removed : removedPainters) {
     removeConfig0("painter.userDefined." + removed.getName());
     myjava.gui.syntax.Painter.remove(removed);
   }
   // highlight?
   boolean _highlightSyntax = highlightSyntax.isSelected();
   boolean _matchBracket = matchBracket.isSelected();
   MyUmbrellaLayerUI.setHighlightingStatus(_highlightSyntax, _matchBracket);
   setConfig("syntax.highlight", _highlightSyntax + "");
   setConfig("syntax.matchBrackets", _matchBracket + "");
 }
public class SyntaxTab extends OptionTab implements Resources {
  // master checkbox: enable/disable all other componenets
  private JCheckBox highlightSyntax =
      new MyCheckBox("Enable syntax highlighting", MyUmbrellaLayerUI.isSyntaxHighlightingEnabled());
  private Set<JComponent> componentSet = new HashSet<>();
  private Set<myjava.gui.syntax.Painter> removedPainters = new HashSet<>();
  // other components
  private JCheckBox matchBracket =
      new MyCheckBox("Enable bracket matching", MyUmbrellaLayerUI.isBracketMatchingEnabled());
  // top: painterComboBox
  private final ItemListener painterChangeListener = new PainterChangeListener();
  private JComboBox<myjava.gui.syntax.Painter> painterComboBox = new JComboBox<>();
  // center: entryListPanels
  private CardLayout cardLayout = new CardLayout();
  private JPanel centerPanel = new JPanel(cardLayout);
  private Set<EntryListPanel> listPanelSet = new TreeSet<>();

  {
    for (myjava.gui.syntax.Painter painter : myjava.gui.syntax.Painter.getPainters()) {
      painterComboBox.addItem(painter);
      EntryListPanel panel = new EntryListPanel(painter);
      listPanelSet.add(panel);
      centerPanel.add(panel, painter.getName());
    }
    componentSet.addAll(Arrays.asList(matchBracket, painterComboBox, centerPanel));
  }

  public SyntaxTab() {
    super(new BorderLayout(), "Syntax highlighting");
    /*
     * upper checkboxes
     */
    JPanel upper = new JPanel(new GridLayout(2, 1, 0, 0));
    upper.setOpaque(false);
    upper.add(MyPanel.wrap(highlightSyntax));
    upper.add(MyPanel.wrap(matchBracket));
    highlightSyntax.addActionListener(
        new ActionListener() {
          @Override
          public void actionPerformed(ActionEvent ev) {
            SyntaxTab.this.updateComponentStatus();
          }
        });
    this.add(upper, BorderLayout.PAGE_START);
    /*
     * upper panel (painters)
     */
    painterComboBox.setSelectedItem(myjava.gui.syntax.Painter.getCurrentInstance());
    painterComboBox.setFont(f13);
    if (isMetal) painterComboBox.setBackground(Color.WHITE);
    painterComboBox.addItemListener(this.painterChangeListener);
    JButton addPainter =
        new MyButton("+") {
          {
            if (isMetal) {
              this.setPreferredSize(new Dimension(28, 28));
            }
          }

          @Override
          public void actionPerformed(ActionEvent ev) {
            String name;
            do {
              name =
                  JOptionPane.showInputDialog(
                      SyntaxTab.this, "Enter a name:", "Name", JOptionPane.QUESTION_MESSAGE);
            } while (!myjava.gui.syntax.Painter.isValidPrompt(name, SyntaxTab.this));
            if ((name != null) && (!name.isEmpty())) {
              // name is valid, neither cancelled nor pressed enter directly
              myjava.gui.syntax.Painter newPainter =
                  ((myjava.gui.syntax.Painter) (painterComboBox.getSelectedItem()))
                      .newInstance(name);
              addPainter(newPainter);
              System.out.println("now set, should call listener");
              painterComboBox.setSelectedItem(newPainter); // auto-call ItemListener(s)
            }
          }
        };
    JButton removePainter =
        new MyButton("-") {
          {
            if (isMetal) {
              this.setPreferredSize(new Dimension(28, 28));
            }
          }

          @Override
          public void actionPerformed(ActionEvent ev) {
            myjava.gui.syntax.Painter painter =
                (myjava.gui.syntax.Painter) (painterComboBox.getSelectedItem());
            if (painter.equals(myjava.gui.syntax.Painter.getDefaultInstance())) {
              JOptionPane.showMessageDialog(
                  SyntaxTab.this,
                  "The default painter cannot be removed.",
                  "Error",
                  JOptionPane.ERROR_MESSAGE);
            } else {
              int option =
                  JOptionPane.showConfirmDialog(
                      SyntaxTab.this,
                      "Remove painter \"" + painter.getName() + "\"?",
                      "Confirm",
                      JOptionPane.YES_NO_OPTION);
              if (option == JOptionPane.YES_OPTION) {
                // remove "painter"
                removedPainters.add(painter);
                painterComboBox.removeItemListener(painterChangeListener);
                painterComboBox.setSelectedItem(myjava.gui.syntax.Painter.getDefaultInstance());
                painterComboBox.removeItem(painter);
                for (Iterator<EntryListPanel> it = listPanelSet.iterator(); it.hasNext(); ) {
                  EntryListPanel panel = it.next();
                  if (panel.getPainter().getName().equals(painter.getName())) {
                    System.out.println("removing, then break");
                    it.remove();
                    centerPanel.remove(panel);
                    break;
                  }
                }
                painterComboBox.addItemListener(painterChangeListener);
                cardLayout.show(
                    centerPanel, myjava.gui.syntax.Painter.getDefaultInstance().getName());
              }
            }
          }
        };
    // lower part
    JPanel center = new JPanel(new BorderLayout());
    JLabel selectLabel = new MyLabel("Selected painter:");
    center.add(
        MyPanel.wrap(MyPanel.CENTER, selectLabel, painterComboBox, addPainter, removePainter),
        BorderLayout.PAGE_START);
    componentSet.addAll(Arrays.asList(selectLabel, addPainter, removePainter));
    center.add(centerPanel, BorderLayout.CENTER);
    this.add(center, BorderLayout.CENTER);
    cardLayout.show(centerPanel, myjava.gui.syntax.Painter.getCurrentInstance().getName());
  }

  private void updateComponentStatus() {
    /*
     * enable/disable component by first checkbox
     */
    boolean enable = highlightSyntax.isSelected();
    for (JComponent c : componentSet) {
      c.setEnabled(enable);
    }
  }

  private void addPainter(myjava.gui.syntax.Painter painter) {
    EntryListPanel newPanel = new EntryListPanel(painter);
    listPanelSet.add(newPanel);
    centerPanel.add(newPanel, painter.getName());
    painterComboBox.removeItemListener(painterChangeListener);
    painterComboBox.removeAllItems();
    for (EntryListPanel p : listPanelSet) {
      painterComboBox.addItem(p.getPainter());
    }
    removedPainters.remove(painter);
    painterComboBox.addItemListener(painterChangeListener);
  }

  private static <E> JList<E> newList(DefaultListModel<E> listModel) {
    JList<E> list = new JList<>(listModel);
    list.setFont(f13);
    list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
    return list;
  }

  class PainterChangeListener implements ItemListener {
    PainterChangeListener() {
      super();
    }

    @Override
    public void itemStateChanged(ItemEvent ev) {
      if (ev.getStateChange() == ItemEvent.SELECTED) {
        SwingUtilities.invokeLater(
            new Runnable() {
              @Override
              public void run() {
                String newPainterName =
                    ((myjava.gui.syntax.Painter) (painterComboBox.getSelectedItem())).getName();
                cardLayout.show(centerPanel, newPainterName);
                centerPanel.revalidate();
                centerPanel.repaint();
              }
            });
      }
    }
  }

  class EntryListPanel extends JPanel implements Comparable<EntryListPanel> {
    private myjava.gui.syntax.Painter painter;
    private DefaultListModel<TypeColorEntry> listModel = new DefaultListModel<>();
    private JList<TypeColorEntry> list = newList(listModel);

    EntryListPanel(myjava.gui.syntax.Painter painter) {
      super(new BorderLayout());
      this.add(new JScrollPane(list), BorderLayout.CENTER);
      this.painter = painter;
      this.updateList();
      this.list.setCellRenderer(new EntryListRenderer());
      this.list.addMouseListener(
          new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent ev) {
              /*
               * selected a colored entry
               */
              if (ev.getClickCount() == 2) {
                if (EntryListPanel.this.painter.equals(
                    myjava.gui.syntax.Painter.getDefaultInstance())) {
                  // cannot modify default painter instance
                  JOptionPane.showMessageDialog(
                      SyntaxTab.this,
                      "The default painter cannot be modified.",
                      "Error",
                      JOptionPane.ERROR_MESSAGE);
                } else {
                  // start modifying, show color chooser dialog
                  final TypeColorEntry entry = list.getSelectedValue();
                  final JColorChooser colorChooser = new JColorChooser(entry.getColor());
                  ActionListener ok =
                      new ActionListener() {
                        @Override
                        public void actionPerformed(ActionEvent ev) {
                          entry.setColor(colorChooser.getColor());
                          list.repaint();
                        }
                      };
                  JDialog dialog =
                      JColorChooser.createDialog(
                          SyntaxTab.this, "Choose a color:", true, colorChooser, ok, null);
                  dialog.setVisible(true);
                }
              }
            }
          });
    }

    myjava.gui.syntax.Painter getPainter() {
      // update painter first
      this.painter =
          TypeColorEntry.toPainter(this.painter.getName(), Collections.list(listModel.elements()));
      return this.painter;
    }

    final void updateList() {
      Set<TypeColorEntry> entries = TypeColorEntry.fromPainter(this.painter);
      this.listModel.removeAllElements();
      for (TypeColorEntry entry : entries) {
        this.listModel.addElement(entry);
      }
    }

    @Override
    public int compareTo(EntryListPanel panel) {
      return this.getPainter().compareTo(panel.getPainter());
    }
  }

  static class TypeColorEntry implements Comparable<TypeColorEntry> {
    private Token.Type type;
    private Color c;

    TypeColorEntry(Token.Type type, Color c) {
      super();
      this.type = type;
      this.c = c;
    }

    Token.Type getType() {
      return this.type;
    }

    Color getColor() {
      return this.c;
    }

    void setColor(Color c) {
      this.c = c;
    }

    static SortedSet<TypeColorEntry> fromPainter(myjava.gui.syntax.Painter painter) {
      Token.Type[] types = Token.Type.values();
      SortedSet<TypeColorEntry> entries = new TreeSet<>();
      for (Token.Type type : types) {
        entries.add(new TypeColorEntry(type, painter.fromType(type)));
      }
      return entries;
    }

    static myjava.gui.syntax.Painter toPainter(
        String name, final Collection<? extends TypeColorEntry> entries) {
      return new myjava.gui.syntax.Painter(name) {
        private Map<Token.Type, Color> map = new HashMap<>();

        {
          for (TypeColorEntry entry : entries) {
            Token.Type type = entry.getType();
            Color color = entry.getColor();
            if ((type == Token.Type.MATCHED_BRACKET) || (type == Token.Type.UNMATCHED_BRACKET)) {
              map.put(type, new Color(color.getRed(), color.getGreen(), color.getBlue(), 80));
            } else {
              map.put(type, color);
            }
          }
        }

        @Override
        public Color fromType(Token.Type type) {
          Color c = map.get(type);
          return c == null ? Color.BLACK : c;
        }
      };
    }

    @Override
    public int compareTo(TypeColorEntry entry) {
      return this.getType().compareTo(entry.getType());
    }
  }

  static class EntryListRenderer extends DefaultListCellRenderer {
    EntryListRenderer() {
      super();
    }

    @Override
    public Component getListCellRendererComponent(
        JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
      JLabel label =
          (JLabel)
              (super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus));
      if (value instanceof TypeColorEntry) {
        TypeColorEntry entry = (TypeColorEntry) value;
        Token.Type type = entry.getType();
        Color color = entry.getColor();
        label.setText(Utilities.normalize(type.toString()));
        if ((type == Token.Type.MATCHED_BRACKET) || (type == Token.Type.UNMATCHED_BRACKET)) {
          label.setBackground(color);
          label.setForeground(Color.BLACK);
        } else {
          label.setForeground(color);
        }
      }
      return label;
    }
  }

  @Override
  public void onExit() {
    // save all painters
    for (EntryListPanel panel : listPanelSet) {
      myjava.gui.syntax.Painter painter = panel.getPainter();
      if (!painter.equals(myjava.gui.syntax.Painter.getDefaultInstance())) {
        myjava.gui.syntax.Painter.add(painter);
        setConfig("painter.userDefined." + painter.getName(), painter.toWritableString());
      }
      if (painter.equals(painterComboBox.getSelectedItem())) {
        // selected painter
        setConfig("syntax.selectedPainter", painter.getName());
        myjava.gui.syntax.Painter.setCurrentInstance(painter);
      }
    }
    // remove "removed painters"
    for (myjava.gui.syntax.Painter removed : removedPainters) {
      removeConfig0("painter.userDefined." + removed.getName());
      myjava.gui.syntax.Painter.remove(removed);
    }
    // highlight?
    boolean _highlightSyntax = highlightSyntax.isSelected();
    boolean _matchBracket = matchBracket.isSelected();
    MyUmbrellaLayerUI.setHighlightingStatus(_highlightSyntax, _matchBracket);
    setConfig("syntax.highlight", _highlightSyntax + "");
    setConfig("syntax.matchBrackets", _matchBracket + "");
  }
}