View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.log4j.chainsaw.color;
19  
20  import org.apache.log4j.chainsaw.ApplicationPreferenceModel;
21  import org.apache.log4j.chainsaw.ChainsawConstants;
22  import org.apache.log4j.chainsaw.ExpressionRuleContext;
23  import org.apache.log4j.chainsaw.filter.FilterModel;
24  import org.apache.log4j.chainsaw.helper.SwingHelper;
25  import org.apache.log4j.chainsaw.icons.ChainsawIcons;
26  import org.apache.log4j.rule.ColorRule;
27  import org.apache.log4j.rule.ExpressionRule;
28  import org.apache.log4j.rule.Rule;
29  
30  import javax.swing.*;
31  import javax.swing.border.Border;
32  import javax.swing.table.DefaultTableModel;
33  import javax.swing.table.TableCellRenderer;
34  import java.awt.*;
35  import java.awt.event.ActionEvent;
36  import java.awt.event.ActionListener;
37  import java.awt.event.ItemEvent;
38  import java.awt.event.ItemListener;
39  import java.util.*;
40  import java.util.List;
41  
42  
43  /**
44   * Panel which updates a RuleColorizer, allowing the user to build expression-based
45   * color rules.
46   * <p>
47   * TODO: examine ColorPanel/RuleColorizer/LogPanel listeners and interactions
48   *
49   * @author Scott Deboy &lt;sdeboy@apache.org&gt;
50   */
51  public class ColorPanel extends JPanel {
52      private static final String DEFAULT_STATUS = "<html>Double click a rule field to edit the rule</html>";
53      private final String currentRuleSet = "Default";
54  
55      private RuleColorizer colorizer;
56      private JPanel rulesPanel;
57      private FilterModel filterModel;
58      private DefaultTableModel tableModel;
59      private JScrollPane tableScrollPane;
60      private JTable table;
61      private ActionListener closeListener;
62      private JLabel statusBar;
63      private Vector<String> columns;
64      private final String noTab = "None";
65      private DefaultComboBoxModel logPanelColorizersModel;
66      private Map<String, RuleColorizer> allLogPanelColorizers;
67      private RuleColorizer currentLogPanelColorizer;
68      private JTable searchTable;
69      private DefaultTableModel searchTableModel;
70      private Vector<String> searchColumns;
71      private Vector<Vector<Color>> searchDataVector;
72      private Vector<Color> searchDataVectorEntry;
73  
74      private JTable alternatingColorTable;
75      private DefaultTableModel alternatingColorTableModel;
76      private Vector<String> alternatingColorColumns;
77      private Vector<Vector<Color>> alternatingColorDataVector;
78      private Vector<Color> alternatingColorDataVectorEntry;
79      private ApplicationPreferenceModel applicationPreferenceModel;
80      private JCheckBox bypassSearchColorsCheckBox;
81  
82      public ColorPanel(final RuleColorizer currentLogPanelColorizer, final FilterModel filterModel,
83                        final Map<String, RuleColorizer> allLogPanelColorizers, final ApplicationPreferenceModel applicationPreferenceModel) {
84          super(new BorderLayout());
85  
86          this.currentLogPanelColorizer = currentLogPanelColorizer;
87          this.colorizer = currentLogPanelColorizer;
88          this.filterModel = filterModel;
89          this.allLogPanelColorizers = allLogPanelColorizers;
90          this.applicationPreferenceModel = applicationPreferenceModel;
91  
92          currentLogPanelColorizer.addPropertyChangeListener(
93              "colorrule",
94              evt -> updateColors());
95  
96          tableModel = new DefaultTableModel();
97          table = new JTable(tableModel);
98          table.setRowHeight(ChainsawConstants.DEFAULT_ROW_HEIGHT);
99  
100         searchTableModel = new DefaultTableModel();
101         searchTable = new JTable(searchTableModel);
102         searchTable.setRowHeight(ChainsawConstants.DEFAULT_ROW_HEIGHT);
103         searchTable.setPreferredScrollableViewportSize(new Dimension(30, 30));
104 
105         alternatingColorTableModel = new DefaultTableModel();
106         alternatingColorTable = new JTable(alternatingColorTableModel);
107         alternatingColorTable.setRowHeight(ChainsawConstants.DEFAULT_ROW_HEIGHT);
108         alternatingColorTable.setPreferredScrollableViewportSize(new Dimension(30, 30));
109 
110         columns = new Vector<>();
111         columns.add("Expression");
112         columns.add("Background");
113         columns.add("Foreground");
114 
115         searchColumns = new Vector<>();
116         searchColumns.add("Background");
117         searchColumns.add("Foreground");
118 
119         alternatingColorColumns = new Vector<>();
120         alternatingColorColumns.add("Background");
121         alternatingColorColumns.add("Foreground");
122 
123         //searchtable contains only a single-entry vector containing a two-item vector (foreground, background)
124         searchDataVector = new Vector<>();
125         searchDataVectorEntry = new Vector<>();
126         searchDataVectorEntry.add(applicationPreferenceModel.getSearchBackgroundColor());
127         searchDataVectorEntry.add(applicationPreferenceModel.getSearchForegroundColor());
128         searchDataVector.add(searchDataVectorEntry);
129         searchTableModel.setDataVector(searchDataVector, searchColumns);
130 
131         alternatingColorDataVector = new Vector<>();
132         alternatingColorDataVectorEntry = new Vector<>();
133         alternatingColorDataVectorEntry.add(applicationPreferenceModel.getAlternatingColorBackgroundColor());
134         alternatingColorDataVectorEntry.add(applicationPreferenceModel.getAlternatingColorForegroundColor());
135         alternatingColorDataVector.add(alternatingColorDataVectorEntry);
136         alternatingColorTableModel.setDataVector(alternatingColorDataVector, alternatingColorColumns);
137 
138         table.setPreferredScrollableViewportSize(new Dimension(525, 200));
139         tableScrollPane = new JScrollPane(table);
140 
141         Vector<Vector<java.io.Serializable>> data = getColorizerVector();
142         tableModel.setDataVector(data, columns);
143 
144         table.sizeColumnsToFit(0);
145         table.getColumnModel().getColumn(1).setPreferredWidth(80);
146         table.getColumnModel().getColumn(2).setPreferredWidth(80);
147         table.getColumnModel().getColumn(1).setMaxWidth(80);
148         table.getColumnModel().getColumn(2).setMaxWidth(80);
149 
150         searchTable.sizeColumnsToFit(0);
151         searchTable.getColumnModel().getColumn(0).setPreferredWidth(80);
152         searchTable.getColumnModel().getColumn(1).setPreferredWidth(80);
153         searchTable.getColumnModel().getColumn(0).setMaxWidth(80);
154         searchTable.getColumnModel().getColumn(1).setMaxWidth(80);
155         //building color choosers needs to be done on the EDT
156         SwingHelper.invokeOnEDT(() -> configureSingleEntryColorTable(searchTable));
157 
158         alternatingColorTable.sizeColumnsToFit(0);
159         alternatingColorTable.getColumnModel().getColumn(0).setPreferredWidth(80);
160         alternatingColorTable.getColumnModel().getColumn(1).setPreferredWidth(80);
161         alternatingColorTable.getColumnModel().getColumn(0).setMaxWidth(80);
162         alternatingColorTable.getColumnModel().getColumn(1).setMaxWidth(80);
163         //building color choosers needs to be done on the EDT
164         SwingHelper.invokeOnEDT(() -> configureSingleEntryColorTable(alternatingColorTable));
165 
166         configureTable();
167 
168         statusBar = new JLabel(DEFAULT_STATUS);
169 
170         rulesPanel = buildRulesPanel();
171         rulesPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
172 
173         JPanel rightPanel = new JPanel(new BorderLayout());
174 
175         JPanel rightOuterPanel = new JPanel();
176         rightOuterPanel.setLayout(
177             new BoxLayout(rightOuterPanel, BoxLayout.X_AXIS));
178         rightOuterPanel.add(Box.createHorizontalStrut(10));
179 
180         JPanel southPanel = new JPanel();
181         southPanel.setLayout(new BoxLayout(southPanel, BoxLayout.Y_AXIS));
182         southPanel.add(Box.createVerticalStrut(5));
183         southPanel.add(Box.createVerticalStrut(5));
184         JPanel searchAndAlternatingColorPanel = buildSearchAndAlternatingColorPanel();
185         JPanel bypassSearchColorsPanel = buildBypassSearchColorsPanel();
186         bypassSearchColorsCheckBox.setSelected(applicationPreferenceModel.isBypassSearchColors());
187 
188         JPanel globalLabelPanel = new JPanel();
189         globalLabelPanel.setLayout(new BoxLayout(globalLabelPanel, BoxLayout.X_AXIS));
190         JLabel globalLabel = new JLabel("Global colors:");
191         globalLabelPanel.add(globalLabel);
192         globalLabelPanel.add(Box.createHorizontalGlue());
193         southPanel.add(globalLabelPanel);
194         southPanel.add(searchAndAlternatingColorPanel);
195         southPanel.add(bypassSearchColorsPanel);
196         southPanel.add(Box.createVerticalStrut(5));
197         JPanel closePanel = buildClosePanel();
198         southPanel.add(closePanel);
199 
200         JPanel statusPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
201         statusPanel.add(statusBar);
202         southPanel.add(statusPanel);
203         rightPanel.add(rulesPanel, BorderLayout.CENTER);
204         rightPanel.add(southPanel, BorderLayout.SOUTH);
205         rightOuterPanel.add(rightPanel);
206 
207         JPanel topPanel = new JPanel();
208         topPanel.setLayout(new BoxLayout(topPanel, BoxLayout.X_AXIS));
209 
210         JLabel selectText = new JLabel("Apply a tab's colors");
211         topPanel.add(selectText);
212         topPanel.add(Box.createHorizontalStrut(5));
213 
214         logPanelColorizersModel = new DefaultComboBoxModel();
215         final JComboBox loadPanelColorizersComboBox = new JComboBox(logPanelColorizersModel);
216         loadLogPanelColorizers();
217 
218         topPanel.add(loadPanelColorizersComboBox);
219 
220         topPanel.add(Box.createHorizontalStrut(5));
221         final Action copyRulesAction = new AbstractAction() {
222             public void actionPerformed(ActionEvent e) {
223                 tableModel.getDataVector().clear();
224                 Object selectedItem = loadPanelColorizersComboBox.getSelectedItem();
225                 if (selectedItem != null) {
226                     RuleColorizer sourceColorizer = allLogPanelColorizers.get(selectedItem.toString());
227                     colorizer.setRules(sourceColorizer.getRules());
228                     updateColors();
229                 }
230             }
231         };
232 
233         loadPanelColorizersComboBox.addActionListener(e -> {
234             Object selectedItem = loadPanelColorizersComboBox.getSelectedItem();
235             if (selectedItem != null) {
236                 String selectedColorizerName = selectedItem.toString();
237                 copyRulesAction.setEnabled(!(noTab.equals(selectedColorizerName)));
238             }
239         });
240 
241         copyRulesAction.putValue(Action.NAME, "Copy color rules");
242         copyRulesAction.setEnabled(!(noTab.equals(loadPanelColorizersComboBox.getSelectedItem())));
243 
244         JButton copyRulesButton = new JButton(copyRulesAction);
245         topPanel.add(copyRulesButton);
246 
247         add(topPanel, BorderLayout.NORTH);
248         add(rightOuterPanel, BorderLayout.CENTER);
249         if (table.getRowCount() > 0) {
250             table.getSelectionModel().setSelectionInterval(0, 0);
251         }
252     }
253 
254     public void loadLogPanelColorizers() {
255         if (logPanelColorizersModel.getIndexOf(noTab) == -1) {
256             logPanelColorizersModel.addElement(noTab);
257         }
258         for (Object o : allLogPanelColorizers.entrySet()) {
259             Map.Entry entry = (Map.Entry) o;
260             if (!entry.getValue().equals(currentLogPanelColorizer) && (logPanelColorizersModel.getIndexOf(entry.getKey()) == -1)) {
261                 logPanelColorizersModel.addElement(entry.getKey());
262             }
263         }
264         //update search and alternating colors, since they may have changed from another color panel
265         searchDataVectorEntry.set(0, applicationPreferenceModel.getSearchBackgroundColor());
266         searchDataVectorEntry.set(1, applicationPreferenceModel.getSearchForegroundColor());
267         alternatingColorDataVectorEntry.set(0, applicationPreferenceModel.getAlternatingColorBackgroundColor());
268         alternatingColorDataVectorEntry.set(1, applicationPreferenceModel.getAlternatingColorForegroundColor());
269     }
270 
271     public JPanel buildBypassSearchColorsPanel() {
272         JPanel panel = new JPanel();
273         panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
274 
275         bypassSearchColorsCheckBox = new JCheckBox("Don't use a search color for matching rows");
276         panel.add(bypassSearchColorsCheckBox);
277         return panel;
278     }
279 
280     public JPanel buildSearchAndAlternatingColorPanel() {
281         JPanel panel = new JPanel();
282         panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
283 
284         JLabel defineSearchColorsLabel = new JLabel("Find colors");
285 
286         panel.add(defineSearchColorsLabel);
287 
288         panel.add(Box.createHorizontalStrut(10));
289         JScrollPane searchPane = new JScrollPane(searchTable);
290         searchPane.setBorder(BorderFactory.createEmptyBorder());
291         panel.add(searchPane);
292 
293         panel.add(Box.createHorizontalStrut(10));
294         JLabel defineAlternatingColorLabel = new JLabel("Alternating colors");
295 
296         panel.add(defineAlternatingColorLabel);
297 
298         panel.add(Box.createHorizontalStrut(10));
299         JScrollPane alternatingColorPane = new JScrollPane(alternatingColorTable);
300         alternatingColorPane.setBorder(BorderFactory.createEmptyBorder());
301 
302         panel.add(alternatingColorPane);
303         panel.setBorder(BorderFactory.createEtchedBorder());
304         panel.add(Box.createHorizontalGlue());
305         return panel;
306     }
307 
308     public void updateColors() {
309         tableModel.getDataVector().clear();
310         tableModel.getDataVector().addAll(getColorizerVector());
311         tableModel.fireTableDataChanged();
312     }
313 
314     private Vector<Vector<java.io.Serializable>> getColorizerVector() {
315         Vector<Vector<java.io.Serializable>> data = new Vector<>();
316         Map map = colorizer.getRules();
317         for (Object o1 : map.entrySet()) {
318             Map.Entry entry = (Map.Entry) o1;
319             //update ruleset list
320             if (entry.getKey().equals(currentRuleSet)) {
321 
322                 for (Object o : ((List) entry.getValue())) {
323                     ColorRule rule = (ColorRule) o;
324                     Vector<java.io.Serializable> v = new Vector<>();
325                     v.add(rule.getExpression());
326                     v.add(rule.getBackgroundColor());
327                     v.add(rule.getForegroundColor());
328                     data.add(v);
329                 }
330             }
331         }
332         return data;
333     }
334 
335     private void configureTable() {
336         table.setToolTipText("Double click to edit");
337         table.setRowHeight(20);
338         table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
339         table.setColumnSelectionAllowed(false);
340 
341         Vector backgroundColors = colorizer.getDefaultColors();
342         Vector foregroundColors = colorizer.getDefaultColors();
343         backgroundColors.add("Browse...");
344         foregroundColors.add("Browse...");
345 
346         JComboBox background = new JComboBox(backgroundColors);
347         background.setMaximumRowCount(15);
348         background.setRenderer(new ColorListCellRenderer());
349 
350         JComboBox foreground = new JComboBox(foregroundColors);
351         foreground.setMaximumRowCount(15);
352         foreground.setRenderer(new ColorListCellRenderer());
353 
354         DefaultCellEditor backgroundEditor = new DefaultCellEditor(background);
355         DefaultCellEditor foregroundEditor = new DefaultCellEditor(foreground);
356         JTextField textField = new JTextField();
357         textField.addKeyListener(
358             new ExpressionRuleContext(filterModel, textField));
359         table.getColumnModel().getColumn(0).setCellEditor(
360             new DefaultCellEditor(textField));
361         table.getColumnModel().getColumn(1).setCellEditor(backgroundEditor);
362         table.getColumnModel().getColumn(2).setCellEditor(foregroundEditor);
363 
364         background.addItemListener(new ColorItemListener(background));
365         foreground.addItemListener(new ColorItemListener(foreground));
366 
367         table.getColumnModel().getColumn(0).setCellRenderer(
368             new ExpressionTableCellRenderer());
369         table.getColumnModel().getColumn(1).setCellRenderer(
370             new ColorTableCellRenderer());
371         table.getColumnModel().getColumn(2).setCellRenderer(
372             new ColorTableCellRenderer());
373     }
374 
375     private void configureSingleEntryColorTable(JTable thisTable) {
376         thisTable.setToolTipText("Double click to edit");
377         thisTable.setRowHeight(20);
378         thisTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
379         thisTable.setColumnSelectionAllowed(false);
380 
381         Vector backgroundColors = colorizer.getDefaultColors();
382         Vector foregroundColors = colorizer.getDefaultColors();
383         backgroundColors.add("Browse...");
384         foregroundColors.add("Browse...");
385 
386         JComboBox background = new JComboBox(backgroundColors);
387         background.setMaximumRowCount(15);
388         background.setRenderer(new ColorListCellRenderer());
389 
390         JComboBox foreground = new JComboBox(foregroundColors);
391         foreground.setMaximumRowCount(15);
392         foreground.setRenderer(new ColorListCellRenderer());
393 
394         DefaultCellEditor backgroundEditor = new DefaultCellEditor(background);
395         DefaultCellEditor foregroundEditor = new DefaultCellEditor(foreground);
396         thisTable.getColumnModel().getColumn(0).setCellEditor(backgroundEditor);
397         thisTable.getColumnModel().getColumn(1).setCellEditor(foregroundEditor);
398 
399         background.addItemListener(new ColorItemListener(background));
400         foreground.addItemListener(new ColorItemListener(foreground));
401 
402         thisTable.getColumnModel().getColumn(0).setCellRenderer(
403             new ColorTableCellRenderer());
404         thisTable.getColumnModel().getColumn(1).setCellRenderer(
405             new ColorTableCellRenderer());
406     }
407 
408     public void setCloseActionListener(ActionListener listener) {
409         closeListener = listener;
410     }
411 
412     public void hidePanel() {
413         if (closeListener != null) {
414             closeListener.actionPerformed(null);
415         }
416     }
417 
418     void applyRules(String ruleSet, RuleColorizer applyingColorizer) {
419         table.getColumnModel().getColumn(0).getCellEditor().stopCellEditing();
420 
421         List list = new ArrayList();
422         Vector vector = tableModel.getDataVector();
423         StringBuilder result = new StringBuilder();
424 
425         for (int i = 0; i < vector.size(); i++) {
426             Vector v = (Vector) vector.elementAt(i);
427 
428             try {
429                 Rule expressionRule = ExpressionRule.getRule((String) v.elementAt(0));
430                 Color background = getBackground();
431                 Color foreground = getForeground();
432 
433                 if (v.elementAt(1) instanceof Color) {
434                     background = (Color) v.elementAt(1);
435                 }
436 
437                 if (v.elementAt(2) instanceof Color) {
438                     foreground = (Color) v.elementAt(2);
439                 }
440 
441                 ColorRule r = new ColorRule((String) v.elementAt(0), expressionRule, background, foreground);
442                 list.add(r);
443             } catch (IllegalArgumentException iae) {
444                 if (!result.toString().equals("")) {
445                     result.append("<br>");
446                 }
447 
448                 result.append(iae.getMessage());
449             }
450         }
451 
452         //all rules are valid, they can be applied
453         if (result.toString().equals("")) {
454             ((ExpressionTableCellRenderer) table.getColumnModel().getColumn(0).getCellRenderer())
455                 .setToolTipText("Double click to edit");
456             statusBar.setText(DEFAULT_STATUS);
457 
458             //only update rules if there were no errors
459             Map map = new HashMap();
460             map.put(ruleSet, list);
461             applyingColorizer.setRules(map);
462 
463         } else {
464             statusBar.setText("Errors - see expression tooltip (color filters won't be active until errors are resolved)");
465             ((ExpressionTableCellRenderer) table.getColumnModel().getColumn(0).getCellRenderer())
466                 .setToolTipText("<html>" + result.toString() + "</html>");
467         }
468     }
469 
470     JPanel buildClosePanel() {
471         JPanel panel = new JPanel();
472         panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
473         panel.add(Box.createHorizontalGlue());
474 
475         JButton saveAsDefaultButton = new JButton(" Save as default ");
476 
477         saveAsDefaultButton.addActionListener(
478             new AbstractAction() {
479                 public void actionPerformed(ActionEvent evt) {
480                     RuleColorizer defaultColorizer = allLogPanelColorizers.get(ChainsawConstants.DEFAULT_COLOR_RULE_NAME);
481                     applyRules(currentRuleSet, defaultColorizer);
482                 }
483             });
484 
485         panel.add(saveAsDefaultButton);
486 
487         JButton applyButton = new JButton(" Apply ");
488 
489         applyButton.addActionListener(
490             new AbstractAction() {
491                 public void actionPerformed(ActionEvent evt) {
492                     applyRules(currentRuleSet, colorizer);
493                     saveSearchColors();
494                     saveAlternatingColors();
495                     saveBypassFlag();
496                 }
497             });
498 
499         panel.add(Box.createHorizontalStrut(10));
500         panel.add(applyButton);
501 
502         JButton closeButton = new JButton(" Close ");
503 
504         closeButton.addActionListener(
505             new AbstractAction() {
506                 public void actionPerformed(ActionEvent evt) {
507                     hidePanel();
508                 }
509             });
510         panel.add(Box.createHorizontalStrut(10));
511         panel.add(closeButton);
512 
513         return panel;
514     }
515 
516     private void saveSearchColors() {
517         Vector thisVector = (Vector) searchTableModel.getDataVector().get(0);
518         applicationPreferenceModel.setSearchBackgroundColor((Color) thisVector.get(0));
519         applicationPreferenceModel.setSearchForegroundColor((Color) thisVector.get(1));
520     }
521 
522     private void saveAlternatingColors() {
523         Vector thisVector = (Vector) alternatingColorTableModel.getDataVector().get(0);
524         applicationPreferenceModel.setAlternatingBackgroundColor((Color) thisVector.get(0));
525         Color alternatingColorForegroundColor = (Color) thisVector.get(1);
526         applicationPreferenceModel.setAlternatingForegroundColor(alternatingColorForegroundColor);
527     }
528 
529     private void saveBypassFlag() {
530         applicationPreferenceModel.setBypassSearchColors(bypassSearchColorsCheckBox.isSelected());
531     }
532 
533     JPanel buildUpDownPanel() {
534         JPanel panel = new JPanel(new BorderLayout());
535         JPanel innerPanel = new JPanel();
536         innerPanel.setLayout(new GridLayout(5, 1));
537 
538         final JButton upButton = new JButton(ChainsawIcons.ICON_UP);
539         upButton.setToolTipText("Move selected rule up");
540 
541         final JButton downButton = new JButton(ChainsawIcons.ICON_DOWN);
542         downButton.setToolTipText("Move selected rule down");
543         upButton.setEnabled(false);
544         downButton.setEnabled(false);
545 
546         table.getSelectionModel().addListSelectionListener(
547             e -> {
548                 if (!e.getValueIsAdjusting()) {
549                     int index = table.getSelectionModel().getMaxSelectionIndex();
550 
551                     if (index < 0) {
552                         downButton.setEnabled(false);
553                         upButton.setEnabled(false);
554                     } else if ((index == 0) && (tableModel.getRowCount() == 1)) {
555                         downButton.setEnabled(false);
556                         upButton.setEnabled(false);
557                     } else if ((index == 0) && (tableModel.getRowCount() > 1)) {
558                         downButton.setEnabled(true);
559                         upButton.setEnabled(false);
560                     } else if (index == (tableModel.getRowCount() - 1)) {
561                         downButton.setEnabled(false);
562                         upButton.setEnabled(true);
563                     } else {
564                         downButton.setEnabled(true);
565                         upButton.setEnabled(true);
566                     }
567                 }
568             });
569 
570         JPanel upPanel = new JPanel();
571 
572         upPanel.add(upButton);
573 
574         JPanel downPanel = new JPanel();
575         downPanel.add(downButton);
576 
577         innerPanel.add(new JLabel(""));
578         innerPanel.add(upPanel);
579         innerPanel.add(new JLabel(""));
580         innerPanel.add(downPanel);
581         panel.add(innerPanel, BorderLayout.CENTER);
582 
583         upButton.addActionListener(
584             new AbstractAction() {
585                 public void actionPerformed(ActionEvent evt) {
586                     int index = table.getSelectionModel().getMaxSelectionIndex();
587 
588                     if (index > 0) {
589                         Vector v = tableModel.getDataVector();
590                         Vector row = (Vector) v.elementAt(index);
591                         tableModel.removeRow(index);
592                         index = index - 1;
593                         tableModel.insertRow(index, row);
594                         table.getSelectionModel().setSelectionInterval(index, index);
595                     }
596                 }
597             });
598 
599         downButton.addActionListener(
600             new AbstractAction() {
601                 public void actionPerformed(ActionEvent evt) {
602                     int index = table.getSelectionModel().getMaxSelectionIndex();
603 
604                     if ((index > -1) && (index < (tableModel.getRowCount() - 1))) {
605                         Vector v = tableModel.getDataVector();
606                         Vector row = (Vector) v.elementAt(index);
607 
608                         tableModel.removeRow(index);
609                         index = index + 1;
610                         tableModel.insertRow(index, row);
611                         table.getSelectionModel().setSelectionInterval(index, index);
612                     }
613                 }
614             });
615 
616         return panel;
617     }
618 
619     JPanel buildRulesPanel() {
620         JPanel listPanel = new JPanel(new BorderLayout());
621         JPanel panel = new JPanel();
622         panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
623 
624         panel.add(Box.createVerticalStrut(10));
625 
626         JLabel rulesLabel = new JLabel("Rules:");
627 
628         panel.add(rulesLabel);
629 
630         JPanel buttonPanel = new JPanel(new GridLayout(0, 2));
631         buttonPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
632 
633         JPanel newPanel = new JPanel();
634         JButton newButton = new JButton(" New ");
635         newButton.addActionListener(
636             new AbstractAction() {
637                 public void actionPerformed(ActionEvent evt) {
638                     int currentRow = table.getSelectedRow();
639                     Vector<java.io.Serializable> v = new Vector<>();
640                     v.add("");
641                     v.add(Color.white);
642                     v.add(Color.black);
643 
644                     if (currentRow < 0) {
645                         tableModel.addRow(v);
646                         currentRow = table.getRowCount() - 1;
647                     } else {
648                         tableModel.insertRow(currentRow, v);
649                     }
650 
651                     table.getSelectionModel().setSelectionInterval(
652                         currentRow, currentRow);
653                 }
654             });
655 
656         newPanel.add(newButton);
657 
658         JPanel deletePanel = new JPanel();
659         final JButton deleteButton = new JButton(" Delete ");
660         deleteButton.setEnabled(false);
661 
662         deleteButton.addActionListener(
663             new AbstractAction() {
664                 public void actionPerformed(ActionEvent evt) {
665                     int index = table.getSelectionModel().getMaxSelectionIndex();
666 
667                     if ((index > -1) && (index < table.getRowCount())) {
668                         tableModel.removeRow(index);
669 
670                         if (index > 0) {
671                             index = index - 1;
672                         }
673 
674                         if (tableModel.getRowCount() > 0) {
675                             table.getSelectionModel().setSelectionInterval(index, index);
676                         }
677                     }
678                 }
679             });
680 
681         table.getSelectionModel().addListSelectionListener(
682             e -> {
683                 if (!e.getValueIsAdjusting()) {
684                     int index = table.getSelectionModel().getMaxSelectionIndex();
685 
686                     if (index < 0) {
687                         deleteButton.setEnabled(false);
688                     } else {
689                         deleteButton.setEnabled(true);
690                     }
691                 }
692             });
693 
694         deletePanel.add(deleteButton);
695 
696         buttonPanel.add(newPanel);
697         buttonPanel.add(deletePanel);
698 
699         listPanel.add(panel, BorderLayout.NORTH);
700 
701         JPanel tablePanel = new JPanel(new BorderLayout());
702         tableScrollPane.setBorder(BorderFactory.createEtchedBorder());
703         tablePanel.add(tableScrollPane, BorderLayout.CENTER);
704         tablePanel.add(buildUpDownPanel(), BorderLayout.EAST);
705         listPanel.add(tablePanel, BorderLayout.CENTER);
706         listPanel.add(buttonPanel, BorderLayout.SOUTH);
707 
708         return listPanel;
709     }
710 
711     class ColorListCellRenderer extends JLabel implements ListCellRenderer {
712         ColorListCellRenderer() {
713             setOpaque(true);
714         }
715 
716         public Component getListCellRendererComponent(
717             JList list, Object value, int index, boolean isSelected,
718             boolean cellHasFocus) {
719             setText(" ");
720 
721             if (isSelected && (index > -1)) {
722                 setBorder(BorderFactory.createLineBorder(Color.black, 2));
723             } else {
724                 setBorder(BorderFactory.createEmptyBorder());
725             }
726 
727             if (value instanceof Color) {
728                 setBackground((Color) value);
729             } else {
730                 setBackground(Color.white);
731 
732                 if (value != null) {
733                     setText(value.toString());
734                 }
735             }
736 
737             return this;
738         }
739     }
740 
741     class ColorItemListener implements ItemListener {
742         JComboBox box;
743         JDialog dialog;
744         JColorChooser colorChooser;
745         Color lastColor;
746 
747         ColorItemListener(final JComboBox box) {
748             this.box = box;
749             colorChooser = new JColorChooser();
750             dialog =
751                 JColorChooser.createDialog(
752                     box, "Pick a Color", true, //modal
753                     colorChooser,
754                     e -> {
755                         box.insertItemAt(colorChooser.getColor(), 0);
756                         box.setSelectedIndex(0);
757                     }, //OK button handler
758                     e -> box.setSelectedItem(lastColor)); //CANCEL button handler
759             dialog.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
760         }
761 
762         public void itemStateChanged(ItemEvent e) {
763             if (e.getStateChange() == ItemEvent.SELECTED) {
764                 if (box.getSelectedItem() instanceof Color) {
765                     box.setBackground((Color) box.getSelectedItem());
766                     repaint();
767                 } else {
768                     box.setBackground(Color.white);
769                     int selectedRow = table.getSelectedRow();
770                     int selectedColumn = table.getSelectedColumn();
771                     if (selectedRow != -1 && selectedColumn != -1) {
772                         colorChooser.setColor((Color) table.getValueAt(selectedRow, selectedColumn));
773                         lastColor = (Color) table.getValueAt(selectedRow, selectedColumn);
774                     }
775                     dialog.setVisible(true);
776                 }
777             }
778         }
779     }
780 
781     class ColorTableCellRenderer implements TableCellRenderer {
782         Border border;
783         JPanel panel;
784 
785         ColorTableCellRenderer() {
786             panel = new JPanel();
787             panel.setOpaque(true);
788         }
789 
790         public Color getCurrentColor() {
791             return panel.getBackground();
792         }
793 
794         public Component getTableCellRendererComponent(
795             JTable thisTable, Object value, boolean isSelected, boolean hasFocus, int row,
796             int column) {
797             if (value instanceof Color) {
798                 panel.setBackground((Color) value);
799             }
800             if (border == null) {
801                 border = BorderFactory.createMatteBorder(2, 2, 2, 2, table.getBackground());
802             }
803 
804             panel.setBorder(border);
805 
806             return panel;
807         }
808     }
809 
810     class ExpressionTableCellRenderer implements TableCellRenderer {
811         JPanel panel = new JPanel();
812         JLabel expressionLabel = new JLabel();
813         JLabel iconLabel = new JLabel();
814         Icon selectedIcon = new SelectedIcon(true);
815         Icon unselectedIcon = new SelectedIcon(false);
816 
817         ExpressionTableCellRenderer() {
818             panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
819             panel.setOpaque(true);
820             panel.add(iconLabel);
821             panel.add(Box.createHorizontalStrut(5));
822             panel.add(expressionLabel);
823         }
824 
825         void setToolTipText(String text) {
826             panel.setToolTipText(text);
827         }
828 
829         public Component getTableCellRendererComponent(
830             JTable thisTable, Object value, boolean isSelected, boolean hasFocus, int row,
831             int column) {
832             if (value == null) {
833                 return panel;
834             }
835 
836             Vector v = tableModel.getDataVector();
837             Vector r = (Vector) v.elementAt(row);
838             expressionLabel.setText(value.toString());
839 
840             if (r.elementAt(1) instanceof Color) {
841                 expressionLabel.setBackground((Color) r.elementAt(1));
842                 panel.setBackground((Color) r.elementAt(1));
843             }
844 
845             if (r.elementAt(2) instanceof Color) {
846                 expressionLabel.setForeground((Color) r.elementAt(2));
847                 panel.setForeground((Color) r.elementAt(2));
848             }
849 
850             if (isSelected) {
851                 iconLabel.setIcon(selectedIcon);
852             } else {
853                 iconLabel.setIcon(unselectedIcon);
854             }
855 
856             return panel;
857         }
858     }
859 
860     class SelectedIcon implements Icon {
861         private boolean isSelected;
862         private int width = 9;
863         private int height = 18;
864         private int[] xPoints = new int[4];
865         private int[] yPoints = new int[4];
866 
867         public SelectedIcon(boolean isSelected) {
868             this.isSelected = isSelected;
869             xPoints[0] = 0;
870             yPoints[0] = -1;
871             xPoints[1] = 0;
872             yPoints[1] = height;
873             xPoints[2] = width;
874             yPoints[2] = height / 2;
875             xPoints[3] = width;
876             yPoints[3] = (height / 2) - 1;
877         }
878 
879         public int getIconHeight() {
880             return height;
881         }
882 
883         public int getIconWidth() {
884             return width;
885         }
886 
887         public void paintIcon(Component c, Graphics g, int x, int y) {
888             if (isSelected) {
889                 int length = xPoints.length;
890                 int[] newXPoints = new int[length];
891                 int[] newYPoints = new int[length];
892 
893                 for (int i = 0; i < length; i++) {
894                     newXPoints[i] = xPoints[i] + x;
895                     newYPoints[i] = yPoints[i] + y;
896                 }
897 
898                 g.setColor(Color.black);
899 
900                 g.fillPolygon(newXPoints, newYPoints, length);
901             }
902         }
903     }
904 }