2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
23 import jalview.api.FeatureColourI;
24 import jalview.api.FeatureSettingsControllerI;
25 import jalview.datamodel.AlignmentI;
26 import jalview.datamodel.SequenceI;
27 import jalview.datamodel.features.FeatureMatcher;
28 import jalview.datamodel.features.FeatureMatcherI;
29 import jalview.datamodel.features.FeatureMatcherSet;
30 import jalview.datamodel.features.FeatureMatcherSetI;
31 import jalview.gui.Help.HelpId;
32 import jalview.gui.JalviewColourChooser.ColourChooserListener;
33 import jalview.io.JalviewFileChooser;
34 import jalview.io.JalviewFileView;
35 import jalview.schemes.FeatureColour;
36 import jalview.util.MessageManager;
37 import jalview.util.Platform;
38 import jalview.viewmodel.seqfeatures.FeatureRendererModel.FeatureSettingsBean;
39 import jalview.xml.binding.jalview.JalviewUserColours;
40 import jalview.xml.binding.jalview.JalviewUserColours.Colour;
41 import jalview.xml.binding.jalview.JalviewUserColours.Filter;
42 import jalview.xml.binding.jalview.ObjectFactory;
44 import java.awt.BorderLayout;
45 import java.awt.Color;
46 import java.awt.Component;
47 import java.awt.Dimension;
49 import java.awt.Graphics;
50 import java.awt.GridLayout;
51 import java.awt.Point;
52 import java.awt.Rectangle;
53 import java.awt.event.ActionEvent;
54 import java.awt.event.ActionListener;
55 import java.awt.event.ItemEvent;
56 import java.awt.event.ItemListener;
57 import java.awt.event.MouseAdapter;
58 import java.awt.event.MouseEvent;
59 import java.awt.event.MouseMotionAdapter;
60 import java.beans.PropertyChangeEvent;
61 import java.beans.PropertyChangeListener;
63 import java.io.FileInputStream;
64 import java.io.FileOutputStream;
65 import java.io.InputStreamReader;
66 import java.io.OutputStreamWriter;
67 import java.io.PrintWriter;
68 import java.util.Arrays;
69 import java.util.Comparator;
70 import java.util.HashMap;
71 import java.util.HashSet;
72 import java.util.Hashtable;
73 import java.util.Iterator;
74 import java.util.List;
78 import javax.help.HelpSetException;
79 import javax.swing.AbstractCellEditor;
80 import javax.swing.BorderFactory;
81 import javax.swing.Icon;
82 import javax.swing.JButton;
83 import javax.swing.JCheckBox;
84 import javax.swing.JCheckBoxMenuItem;
85 import javax.swing.JInternalFrame;
86 import javax.swing.JLabel;
87 import javax.swing.JLayeredPane;
88 import javax.swing.JMenuItem;
89 import javax.swing.JPanel;
90 import javax.swing.JPopupMenu;
91 import javax.swing.JScrollPane;
92 import javax.swing.JSlider;
93 import javax.swing.JTable;
94 import javax.swing.ListSelectionModel;
95 import javax.swing.SwingConstants;
96 import javax.swing.ToolTipManager;
97 import javax.swing.border.Border;
98 import javax.swing.event.ChangeEvent;
99 import javax.swing.event.ChangeListener;
100 import javax.swing.table.AbstractTableModel;
101 import javax.swing.table.JTableHeader;
102 import javax.swing.table.TableCellEditor;
103 import javax.swing.table.TableCellRenderer;
104 import javax.swing.table.TableColumn;
105 import javax.xml.bind.JAXBContext;
106 import javax.xml.bind.JAXBElement;
107 import javax.xml.bind.Marshaller;
108 import javax.xml.stream.XMLInputFactory;
109 import javax.xml.stream.XMLStreamReader;
111 public class FeatureSettings extends JPanel
112 implements FeatureSettingsControllerI
114 private static final String SEQUENCE_FEATURE_COLOURS = MessageManager
115 .getString("label.sequence_feature_colours");
118 * column indices of fields in Feature Settings table
120 static final int TYPE_COLUMN = 0;
122 static final int COLOUR_COLUMN = 1;
124 static final int FILTER_COLUMN = 2;
126 static final int SHOW_COLUMN = 3;
128 private static final int COLUMN_COUNT = 4;
130 private static final int MIN_WIDTH = 400;
132 private static final int MIN_HEIGHT = 400;
134 private final static String BASE_TOOLTIP = MessageManager.getString("label.click_to_edit");
136 final FeatureRenderer fr;
138 public final AlignFrame af;
141 * 'original' fields hold settings to restore on Cancel
143 Object[][] originalData;
145 float originalTransparency;
147 Map<String, FeatureMatcherSetI> originalFilters;
149 final JInternalFrame frame;
151 JScrollPane scrollPane = new JScrollPane();
157 JSlider transparency = new JSlider();
160 * when true, constructor is still executing - so ignore UI events
162 protected volatile boolean inConstruction = true;
164 int selectedRow = -1;
166 boolean resettingTable = false;
169 * true when Feature Settings are updating from feature renderer
171 boolean handlingUpdate = false;
174 * holds {featureCount, totalExtent} for each feature type
176 Map<String, float[]> typeWidth = null;
183 public FeatureSettings(AlignFrame alignFrame)
185 this.af = alignFrame;
186 fr = af.getFeatureRenderer();
188 // save transparency for restore on Cancel
189 originalTransparency = fr.getTransparency();
190 int originalTransparencyAsPercent = (int) (originalTransparency * 100);
191 transparency.setMaximum(100 - originalTransparencyAsPercent);
193 originalFilters = new HashMap<>(fr.getFeatureFilters()); // shallow copy
198 } catch (Exception ex)
200 ex.printStackTrace();
206 public String getToolTipText(MouseEvent e)
209 int column = table.columnAtPoint(e.getPoint());
210 int row = table.rowAtPoint(e.getPoint());
215 tip = JvSwingUtils.wrapTooltip(true, MessageManager
216 .getString("label.feature_settings_click_drag"));
219 FeatureColourI colour = (FeatureColourI) table.getValueAt(row,
221 tip = getColorTooltip(colour, true);
224 FeatureMatcherSet o = (FeatureMatcherSet) table.getValueAt(row,
228 .getString("label.configure_feature_tooltip")
240 * Position the tooltip near the bottom edge of, and half way across, the
244 public Point getToolTipLocation(MouseEvent e)
246 Point point = e.getPoint();
247 int column = table.columnAtPoint(point);
248 int row = table.rowAtPoint(point);
249 Rectangle r = getCellRect(row, column, false);
250 Point loc = new Point(r.x + r.width / 2, r.y + r.height - 3);
254 JTableHeader tableHeader = table.getTableHeader();
255 tableHeader.setFont(new Font("Verdana", Font.PLAIN, 12));
256 tableHeader.setReorderingAllowed(false);
257 table.setFont(new Font("Verdana", Font.PLAIN, 12));
258 table.setDefaultEditor(FeatureColour.class, new ColorEditor());
259 table.setDefaultRenderer(FeatureColour.class, new ColorRenderer());
261 table.setDefaultEditor(FeatureMatcherSet.class, new FilterEditor());
262 table.setDefaultRenderer(FeatureMatcherSet.class, new FilterRenderer());
264 TableColumn colourColumn = new TableColumn(COLOUR_COLUMN, 75,
265 new ColorRenderer(), new ColorEditor());
266 table.addColumn(colourColumn);
268 TableColumn filterColumn = new TableColumn(FILTER_COLUMN, 75,
269 new FilterRenderer(), new FilterEditor());
270 table.addColumn(filterColumn);
272 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
274 table.addMouseListener(new MouseAdapter()
277 public void mousePressed(MouseEvent evt)
279 selectedRow = table.rowAtPoint(evt.getPoint());
280 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
281 if (evt.isPopupTrigger())
283 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
284 showPopupMenu(selectedRow, type, colour, evt.getPoint());
286 else if (evt.getClickCount() == 2)
288 boolean invertSelection = evt.isAltDown();
289 boolean toggleSelection = Platform.isControlDown(evt);
290 boolean extendSelection = evt.isShiftDown();
291 fr.ap.alignFrame.avc.markColumnsContainingFeatures(
292 invertSelection, extendSelection, toggleSelection, type);
296 // isPopupTrigger fires on mouseReleased on Windows
298 public void mouseReleased(MouseEvent evt)
300 selectedRow = table.rowAtPoint(evt.getPoint());
301 if (evt.isPopupTrigger())
303 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
304 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
305 showPopupMenu(selectedRow, type, colour, evt.getPoint());
310 table.addMouseMotionListener(new MouseMotionAdapter()
313 public void mouseDragged(MouseEvent evt)
315 int newRow = table.rowAtPoint(evt.getPoint());
316 if (newRow != selectedRow && selectedRow != -1 && newRow != -1)
319 * reposition 'selectedRow' to 'newRow' (the dragged to location)
320 * this could be more than one row away for a very fast drag action
321 * so just swap it with adjacent rows until we get it there
323 Object[][] data = ((FeatureTableModel) table.getModel())
325 int direction = newRow < selectedRow ? -1 : 1;
326 for (int i = selectedRow; i != newRow; i += direction)
328 Object[] temp = data[i];
329 data[i] = data[i + direction];
330 data[i + direction] = temp;
332 updateFeatureRenderer(data);
334 selectedRow = newRow;
338 // table.setToolTipText(JvSwingUtils.wrapTooltip(true,
339 // MessageManager.getString("label.feature_settings_click_drag")));
340 scrollPane.setViewportView(table);
342 if (af.getViewport().isShowSequenceFeatures() || !fr.hasRenderOrder())
344 fr.findAllFeatures(true); // display everything!
347 discoverAllFeatureData();
348 final PropertyChangeListener change;
349 final FeatureSettings fs = this;
350 fr.addPropertyChangeListener(change = new PropertyChangeListener()
353 public void propertyChange(PropertyChangeEvent evt)
355 if (!fs.resettingTable && !fs.handlingUpdate)
357 fs.handlingUpdate = true;
359 // new groups may be added with new sequence feature types only
360 fs.handlingUpdate = false;
366 frame = new JInternalFrame();
367 frame.setContentPane(this);
368 Desktop.addInternalFrame(frame,
369 MessageManager.getString("label.sequence_feature_settings"),
370 600, Platform.isAMacAndNotJS() ? 480 : 450);
371 frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
373 frame.addInternalFrameListener(
374 new javax.swing.event.InternalFrameAdapter()
377 public void internalFrameClosed(
378 javax.swing.event.InternalFrameEvent evt)
380 fr.removePropertyChangeListener(change);
383 frame.setLayer(JLayeredPane.PALETTE_LAYER);
384 inConstruction = false;
388 * Constructs and shows a popup menu of possible actions on the selected row and
396 protected void showPopupMenu(final int rowSelected, final String type,
397 final Object typeCol, final Point pt)
399 JPopupMenu men = new JPopupMenu(MessageManager
400 .formatMessage("label.settings_for_param", new String[]
402 final FeatureColourI featureColour = (FeatureColourI) typeCol;
405 * menu option to select (or deselect) variable colour
407 final JCheckBoxMenuItem variableColourCB = new JCheckBoxMenuItem(
408 MessageManager.getString("label.variable_colour"));
409 variableColourCB.setSelected(!featureColour.isSimpleColour());
410 men.add(variableColourCB);
413 * checkbox action listener doubles up as listener to OK
414 * from the variable colour / filters dialog
416 variableColourCB.addActionListener(new ActionListener()
419 public void actionPerformed(ActionEvent e)
421 if (e.getSource() == variableColourCB)
423 men.setVisible(true); // BH 2018 for JavaScript because this is a checkbox
424 men.setVisible(false); // BH 2018 for JavaScript because this is a checkbox
425 if (featureColour.isSimpleColour())
428 * toggle simple colour to variable colour - show dialog
430 FeatureTypeSettings fc = new FeatureTypeSettings(fr, type);
431 fc.addActionListener(this);
436 * toggle variable to simple colour - show colour chooser
438 String title = MessageManager.formatMessage("label.select_colour_for", type);
439 ColourChooserListener listener = new ColourChooserListener()
442 public void colourSelected(Color c)
444 table.setValueAt(new FeatureColour(c), rowSelected,
447 updateFeatureRenderer(
448 ((FeatureTableModel) table.getModel()).getData(),
452 JalviewColourChooser.showColourChooser(FeatureSettings.this, title,
453 featureColour.getMaxColour(), listener);
458 if (e.getSource() instanceof FeatureTypeSettings)
461 * update after OK in feature colour dialog; the updated
462 * colour will have already been set in the FeatureRenderer
464 FeatureColourI fci = fr.getFeatureColours().get(type);
465 table.setValueAt(fci, rowSelected, COLOUR_COLUMN);
466 // BH 2018 setting a table value does not invalidate it.
467 // System.out.println("FeatureSettings is valied" + table.isValid());
476 JMenuItem scr = new JMenuItem(
477 MessageManager.getString("label.sort_by_score"));
479 scr.addActionListener(new ActionListener()
483 public void actionPerformed(ActionEvent e)
485 af.avc.sortAlignmentByFeatureScore(Arrays.asList(new String[]
489 JMenuItem dens = new JMenuItem(
490 MessageManager.getString("label.sort_by_density"));
491 dens.addActionListener(new ActionListener()
495 public void actionPerformed(ActionEvent e)
497 af.avc.sortAlignmentByFeatureDensity(Arrays.asList(new String[]
503 JMenuItem selCols = new JMenuItem(
504 MessageManager.getString("label.select_columns_containing"));
505 selCols.addActionListener(new ActionListener()
508 public void actionPerformed(ActionEvent arg0)
510 fr.ap.alignFrame.avc.markColumnsContainingFeatures(false, false,
514 JMenuItem clearCols = new JMenuItem(MessageManager
515 .getString("label.select_columns_not_containing"));
516 clearCols.addActionListener(new ActionListener()
519 public void actionPerformed(ActionEvent arg0)
521 fr.ap.alignFrame.avc.markColumnsContainingFeatures(true, false,
525 JMenuItem hideCols = new JMenuItem(
526 MessageManager.getString("label.hide_columns_containing"));
527 hideCols.addActionListener(new ActionListener()
530 public void actionPerformed(ActionEvent arg0)
532 fr.ap.alignFrame.hideFeatureColumns(type, true);
535 JMenuItem hideOtherCols = new JMenuItem(
536 MessageManager.getString("label.hide_columns_not_containing"));
537 hideOtherCols.addActionListener(new ActionListener()
540 public void actionPerformed(ActionEvent arg0)
542 fr.ap.alignFrame.hideFeatureColumns(type, false);
548 men.add(hideOtherCols);
549 men.show(table, pt.x, pt.y);
553 synchronized public void discoverAllFeatureData()
555 Set<String> allGroups = new HashSet<>();
556 AlignmentI alignment = af.getViewport().getAlignment();
558 for (int i = 0; i < alignment.getHeight(); i++)
560 SequenceI seq = alignment.getSequenceAt(i);
561 for (String group : seq.getFeatures().getFeatureGroups(true))
563 if (group != null && !allGroups.contains(group))
565 allGroups.add(group);
566 checkGroupState(group);
577 * Synchronise gui group list and check visibility of group
580 * @return true if group is visible
582 private boolean checkGroupState(String group)
584 boolean visible = fr.checkGroupVisibility(group, true);
586 for (int g = 0; g < groupPanel.getComponentCount(); g++)
588 if (((JCheckBox) groupPanel.getComponent(g)).getText().equals(group))
590 ((JCheckBox) groupPanel.getComponent(g)).setSelected(visible);
595 final String grp = group;
596 final JCheckBox check = new JCheckBox(group, visible);
597 check.setFont(new Font("Serif", Font.BOLD, 12));
598 check.setToolTipText(group);
599 check.addItemListener(new ItemListener()
602 public void itemStateChanged(ItemEvent evt)
604 fr.setGroupVisibility(check.getText(), check.isSelected());
605 resetTable(new String[] { grp });
606 af.alignPanel.paintAlignment(true, true);
609 groupPanel.add(check);
613 synchronized void resetTable(String[] groupChanged)
619 resettingTable = true;
620 typeWidth = new Hashtable<>();
621 // TODO: change avWidth calculation to 'per-sequence' average and use long
624 Set<String> displayableTypes = new HashSet<>();
625 Set<String> foundGroups = new HashSet<>();
628 * determine which feature types may be visible depending on
629 * which groups are selected, and recompute average width data
631 for (int i = 0; i < af.getViewport().getAlignment().getHeight(); i++)
634 SequenceI seq = af.getViewport().getAlignment().getSequenceAt(i);
637 * get the sequence's groups for positional features
638 * and keep track of which groups are visible
640 Set<String> groups = seq.getFeatures().getFeatureGroups(true);
641 Set<String> visibleGroups = new HashSet<>();
642 for (String group : groups)
644 if (group == null || checkGroupState(group))
646 visibleGroups.add(group);
649 foundGroups.addAll(groups);
652 * get distinct feature types for visible groups
653 * record distinct visible types, and their count and total length
655 Set<String> types = seq.getFeatures().getFeatureTypesForGroups(true,
656 visibleGroups.toArray(new String[visibleGroups.size()]));
657 for (String type : types)
659 displayableTypes.add(type);
660 float[] avWidth = typeWidth.get(type);
663 avWidth = new float[2];
664 typeWidth.put(type, avWidth);
666 // todo this could include features with a non-visible group
667 // - do we greatly care?
668 // todo should we include non-displayable features here, and only
669 // update when features are added?
670 avWidth[0] += seq.getFeatures().getFeatureCount(true, type);
671 avWidth[1] += seq.getFeatures().getTotalFeatureLength(type);
675 Object[][] data = new Object[displayableTypes.size()][COLUMN_COUNT];
678 if (fr.hasRenderOrder())
682 fr.findAllFeatures(groupChanged != null); // prod to update
683 // colourschemes. but don't
685 // First add the checks in the previous render order,
686 // in case the window has been closed and reopened
688 List<String> frl = fr.getRenderOrder();
689 for (int ro = frl.size() - 1; ro > -1; ro--)
691 String type = frl.get(ro);
693 if (!displayableTypes.contains(type))
698 data[dataIndex][TYPE_COLUMN] = type;
699 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
700 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
701 data[dataIndex][FILTER_COLUMN] = featureFilter == null
702 ? new FeatureMatcherSet()
704 data[dataIndex][SHOW_COLUMN] = new Boolean(
705 af.getViewport().getFeaturesDisplayed().isVisible(type));
707 displayableTypes.remove(type);
712 * process any extra features belonging only to
713 * a group which was just selected
715 while (!displayableTypes.isEmpty())
717 String type = displayableTypes.iterator().next();
718 data[dataIndex][TYPE_COLUMN] = type;
720 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
721 if (data[dataIndex][COLOUR_COLUMN] == null)
723 // "Colour has been updated in another view!!"
724 fr.clearRenderOrder();
727 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
728 data[dataIndex][FILTER_COLUMN] = featureFilter == null
729 ? new FeatureMatcherSet()
731 data[dataIndex][SHOW_COLUMN] = new Boolean(true);
733 displayableTypes.remove(type);
736 if (originalData == null)
738 originalData = new Object[data.length][COLUMN_COUNT];
739 for (int i = 0; i < data.length; i++)
741 System.arraycopy(data[i], 0, originalData[i], 0, COLUMN_COUNT);
746 updateOriginalData(data);
749 table.setModel(new FeatureTableModel(data));
750 table.getColumnModel().getColumn(0).setPreferredWidth(200);
752 groupPanel.setLayout(
753 new GridLayout(fr.getFeatureGroupsSize() / 4 + 1, 4));
754 pruneGroups(foundGroups);
755 groupPanel.validate();
757 updateFeatureRenderer(data, groupChanged != null);
758 resettingTable = false;
762 * Updates 'originalData' (used for restore on Cancel) if we detect that changes
763 * have been made outwith this dialog
765 * <li>a new feature type added (and made visible)</li>
766 * <li>a feature colour changed (in the Amend Features dialog)</li>
771 protected void updateOriginalData(Object[][] foundData)
773 // todo LinkedHashMap instead of Object[][] would be nice
775 Object[][] currentData = ((FeatureTableModel) table.getModel())
777 for (Object[] row : foundData)
779 String type = (String) row[TYPE_COLUMN];
780 boolean found = false;
781 for (Object[] current : currentData)
783 if (type.equals(current[TYPE_COLUMN]))
787 * currently dependent on object equality here;
788 * really need an equals method on FeatureColour
790 if (!row[COLOUR_COLUMN].equals(current[COLOUR_COLUMN]))
793 * feature colour has changed externally - update originalData
795 for (Object[] original : originalData)
797 if (type.equals(original[TYPE_COLUMN]))
799 original[COLOUR_COLUMN] = row[COLOUR_COLUMN];
810 * new feature detected - add to original data (on top)
812 Object[][] newData = new Object[originalData.length
814 for (int i = 0; i < originalData.length; i++)
816 System.arraycopy(originalData[i], 0, newData[i + 1], 0,
820 originalData = newData;
826 * Remove from the groups panel any checkboxes for groups that are not in the
827 * foundGroups set. This enables removing a group from the display when the last
828 * feature in that group is deleted.
832 protected void pruneGroups(Set<String> foundGroups)
834 for (int g = 0; g < groupPanel.getComponentCount(); g++)
836 JCheckBox checkbox = (JCheckBox) groupPanel.getComponent(g);
837 if (!foundGroups.contains(checkbox.getText()))
839 groupPanel.remove(checkbox);
845 * reorder data based on the featureRenderers global priority list.
849 private void ensureOrder(Object[][] data)
851 boolean sort = false;
852 float[] order = new float[data.length];
853 for (int i = 0; i < order.length; i++)
855 order[i] = fr.getOrder(data[i][0].toString());
858 order[i] = fr.setOrder(data[i][0].toString(), i / order.length);
862 sort = sort || order[i - 1] > order[i];
867 jalview.util.QuickSort.sort(order, data);
872 * Offers a file chooser dialog, and then loads the feature colours and
873 * filters from file in XML format and unmarshals to Jalview feature settings
877 JalviewFileChooser chooser = new JalviewFileChooser("fc",
878 SEQUENCE_FEATURE_COLOURS);
879 chooser.setFileView(new JalviewFileView());
880 chooser.setDialogTitle(
881 MessageManager.getString("label.load_feature_colours"));
882 chooser.setToolTipText(MessageManager.getString("action.load"));
883 chooser.setResponseHandler(0, new Runnable()
888 File file = chooser.getSelectedFile();
892 chooser.showOpenDialog(this);
896 * Loads feature colours and filters from XML stored in the given file
904 InputStreamReader in = new InputStreamReader(
905 new FileInputStream(file), "UTF-8");
907 JAXBContext jc = JAXBContext
908 .newInstance("jalview.xml.binding.jalview");
909 javax.xml.bind.Unmarshaller um = jc.createUnmarshaller();
910 XMLStreamReader streamReader = XMLInputFactory.newInstance()
911 .createXMLStreamReader(in);
912 JAXBElement<JalviewUserColours> jbe = um.unmarshal(streamReader,
913 JalviewUserColours.class);
914 JalviewUserColours jucs = jbe.getValue();
916 // JalviewUserColours jucs = JalviewUserColours.unmarshal(in);
919 * load feature colours
921 for (int i = jucs.getColour().size() - 1; i >= 0; i--)
923 Colour newcol = jucs.getColour().get(i);
924 FeatureColourI colour = jalview.project.Jalview2XML
925 .parseColour(newcol);
926 fr.setColour(newcol.getName(), colour);
927 fr.setOrder(newcol.getName(), i / (float) jucs.getColour().size());
931 * load feature filters; loaded filters will replace any that are
932 * currently defined, other defined filters are left unchanged
934 for (int i = 0; i < jucs.getFilter().size(); i++)
936 Filter filterModel = jucs.getFilter().get(i);
937 String featureType = filterModel.getFeatureType();
938 FeatureMatcherSetI filter = jalview.project.Jalview2XML
939 .parseFilter(featureType, filterModel.getMatcherSet());
940 if (!filter.isEmpty())
942 fr.setFeatureFilter(featureType, filter);
947 * update feature settings table
952 Object[][] data = ((FeatureTableModel) table.getModel())
955 updateFeatureRenderer(data, false);
958 } catch (Exception ex)
960 System.out.println("Error loading User Colour File\n" + ex);
965 * Offers a file chooser dialog, and then saves the current feature colours
966 * and any filters to the selected file in XML format
970 JalviewFileChooser chooser = new JalviewFileChooser("fc",
971 SEQUENCE_FEATURE_COLOURS);
972 chooser.setFileView(new JalviewFileView());
973 chooser.setDialogTitle(
974 MessageManager.getString("label.save_feature_colours"));
975 chooser.setToolTipText(MessageManager.getString("action.save"));
976 int option = chooser.showSaveDialog(this);
977 if (option == JalviewFileChooser.APPROVE_OPTION)
979 File file = chooser.getSelectedFile();
985 * Saves feature colours and filters to the given file
991 JalviewUserColours ucs = new JalviewUserColours();
992 ucs.setSchemeName("Sequence Features");
995 PrintWriter out = new PrintWriter(new OutputStreamWriter(
996 new FileOutputStream(file), "UTF-8"));
999 * sort feature types by colour order, from 0 (highest)
1002 Set<String> fr_colours = fr.getAllFeatureColours();
1003 String[] sortedTypes = fr_colours
1004 .toArray(new String[fr_colours.size()]);
1005 Arrays.sort(sortedTypes, new Comparator<String>()
1008 public int compare(String type1, String type2)
1010 return Float.compare(fr.getOrder(type1), fr.getOrder(type2));
1015 * save feature colours
1017 for (String featureType : sortedTypes)
1019 FeatureColourI fcol = fr.getFeatureStyle(featureType);
1020 Colour col = jalview.project.Jalview2XML.marshalColour(featureType,
1022 ucs.getColour().add(col);
1026 * save any feature filters
1028 for (String featureType : sortedTypes)
1030 FeatureMatcherSetI filter = fr.getFeatureFilter(featureType);
1031 if (filter != null && !filter.isEmpty())
1033 Iterator<FeatureMatcherI> iterator = filter.getMatchers().iterator();
1034 FeatureMatcherI firstMatcher = iterator.next();
1035 jalview.xml.binding.jalview.FeatureMatcherSet ms = jalview.project.Jalview2XML
1036 .marshalFilter(firstMatcher, iterator,
1038 Filter filterModel = new Filter();
1039 filterModel.setFeatureType(featureType);
1040 filterModel.setMatcherSet(ms);
1041 ucs.getFilter().add(filterModel);
1044 JAXBContext jaxbContext = JAXBContext
1045 .newInstance(JalviewUserColours.class);
1046 Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
1047 jaxbMarshaller.marshal(
1048 new ObjectFactory().createJalviewUserColours(ucs), out);
1050 // jaxbMarshaller.marshal(object, pout);
1051 // marshaller.marshal(object);
1054 // ucs.marshal(out);
1056 } catch (Exception ex)
1058 ex.printStackTrace();
1062 public void invertSelection()
1064 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1065 for (int i = 0; i < data.length; i++)
1067 data[i][SHOW_COLUMN] = !(Boolean) data[i][SHOW_COLUMN];
1069 updateFeatureRenderer(data, true);
1073 public void orderByAvWidth()
1075 if (table == null || table.getModel() == null)
1079 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1080 float[] width = new float[data.length];
1084 for (int i = 0; i < data.length; i++)
1086 awidth = typeWidth.get(data[i][TYPE_COLUMN]);
1089 width[i] = awidth[1] / awidth[0];// *awidth[0]*awidth[2]; - better
1090 // weight - but have to make per
1091 // sequence, too (awidth[2])
1092 // if (width[i]==1) // hack to distinguish single width sequences.
1103 boolean sort = false;
1104 for (int i = 0; i < width.length; i++)
1106 // awidth = (float[]) typeWidth.get(data[i][0]);
1109 width[i] = fr.getOrder(data[i][TYPE_COLUMN].toString());
1112 width[i] = fr.setOrder(data[i][TYPE_COLUMN].toString(),
1118 width[i] /= max; // normalize
1119 fr.setOrder(data[i][TYPE_COLUMN].toString(), width[i]); // store for later
1123 sort = sort || width[i - 1] > width[i];
1128 jalview.util.QuickSort.sort(width, data);
1129 // update global priority order
1132 updateFeatureRenderer(data, false);
1140 frame.setClosed(true);
1141 } catch (Exception exe)
1147 public void updateFeatureRenderer(Object[][] data)
1149 updateFeatureRenderer(data, true);
1153 * Update the priority order of features; only repaint if this changed the order
1154 * of visible features
1159 void updateFeatureRenderer(Object[][] data, boolean visibleNew)
1161 FeatureSettingsBean[] rowData = getTableAsBeans(data);
1163 if (fr.setFeaturePriority(rowData, visibleNew))
1165 af.alignPanel.paintAlignment(true, true);
1170 * Converts table data into an array of data beans
1172 private FeatureSettingsBean[] getTableAsBeans(Object[][] data)
1174 FeatureSettingsBean[] rowData = new FeatureSettingsBean[data.length];
1175 for (int i = 0; i < data.length; i++)
1177 String type = (String) data[i][TYPE_COLUMN];
1178 FeatureColourI colour = (FeatureColourI) data[i][COLOUR_COLUMN];
1179 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) data[i][FILTER_COLUMN];
1180 Boolean isShown = (Boolean) data[i][SHOW_COLUMN];
1181 rowData[i] = new FeatureSettingsBean(type, colour, theFilter,
1187 private void jbInit() throws Exception
1189 this.setLayout(new BorderLayout());
1191 JPanel settingsPane = new JPanel();
1192 settingsPane.setLayout(new BorderLayout());
1194 JPanel bigPanel = new JPanel();
1195 bigPanel.setLayout(new BorderLayout());
1197 groupPanel = new JPanel();
1198 bigPanel.add(groupPanel, BorderLayout.NORTH);
1200 JButton invert = new JButton(
1201 MessageManager.getString("label.invert_selection"));
1202 invert.setFont(JvSwingUtils.getLabelFont());
1203 invert.addActionListener(new ActionListener()
1206 public void actionPerformed(ActionEvent e)
1212 JButton optimizeOrder = new JButton(
1213 MessageManager.getString("label.optimise_order"));
1214 optimizeOrder.setFont(JvSwingUtils.getLabelFont());
1215 optimizeOrder.addActionListener(new ActionListener()
1218 public void actionPerformed(ActionEvent e)
1224 JButton sortByScore = new JButton(
1225 MessageManager.getString("label.seq_sort_by_score"));
1226 sortByScore.setFont(JvSwingUtils.getLabelFont());
1227 sortByScore.addActionListener(new ActionListener()
1230 public void actionPerformed(ActionEvent e)
1232 af.avc.sortAlignmentByFeatureScore(null);
1235 JButton sortByDens = new JButton(
1236 MessageManager.getString("label.sequence_sort_by_density"));
1237 sortByDens.setFont(JvSwingUtils.getLabelFont());
1238 sortByDens.addActionListener(new ActionListener()
1241 public void actionPerformed(ActionEvent e)
1243 af.avc.sortAlignmentByFeatureDensity(null);
1247 JButton help = new JButton(MessageManager.getString("action.help"));
1248 help.setFont(JvSwingUtils.getLabelFont());
1249 help.addActionListener(new ActionListener()
1252 public void actionPerformed(ActionEvent e)
1256 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1257 } catch (HelpSetException e1)
1259 e1.printStackTrace();
1264 JButton cancel = new JButton(MessageManager.getString("action.cancel"));
1265 cancel.setFont(JvSwingUtils.getLabelFont());
1266 cancel.addActionListener(new ActionListener()
1269 public void actionPerformed(ActionEvent e)
1271 fr.setTransparency(originalTransparency);
1272 fr.setFeatureFilters(originalFilters);
1273 updateFeatureRenderer(originalData);
1278 JButton ok = new JButton(MessageManager.getString("action.ok"));
1279 ok.setFont(JvSwingUtils.getLabelFont());
1280 ok.addActionListener(new ActionListener()
1283 public void actionPerformed(ActionEvent e)
1289 JButton loadColours = new JButton(
1290 MessageManager.getString("label.load_colours"));
1291 loadColours.setFont(JvSwingUtils.getLabelFont());
1292 loadColours.setToolTipText(
1293 MessageManager.getString("label.load_colours_tooltip"));
1294 loadColours.addActionListener(new ActionListener()
1297 public void actionPerformed(ActionEvent e)
1303 JButton saveColours = new JButton(
1304 MessageManager.getString("label.save_colours"));
1305 saveColours.setFont(JvSwingUtils.getLabelFont());
1306 saveColours.setToolTipText(
1307 MessageManager.getString("label.save_colours_tooltip"));
1308 saveColours.addActionListener(new ActionListener()
1311 public void actionPerformed(ActionEvent e)
1316 transparency.addChangeListener(new ChangeListener()
1319 public void stateChanged(ChangeEvent evt)
1321 if (!inConstruction)
1323 fr.setTransparency((100 - transparency.getValue()) / 100f);
1324 af.alignPanel.paintAlignment(true, true);
1329 transparency.setMaximum(70);
1330 transparency.setToolTipText(
1331 MessageManager.getString("label.transparency_tip"));
1333 JPanel transPanel = new JPanel(new GridLayout(1, 2));
1334 bigPanel.add(transPanel, BorderLayout.SOUTH);
1336 JPanel transbuttons = new JPanel(new GridLayout(5, 1));
1337 transbuttons.add(optimizeOrder);
1338 transbuttons.add(invert);
1339 transbuttons.add(sortByScore);
1340 transbuttons.add(sortByDens);
1341 transbuttons.add(help);
1342 transPanel.add(transparency);
1343 transPanel.add(transbuttons);
1345 JPanel buttonPanel = new JPanel();
1346 buttonPanel.add(ok);
1347 buttonPanel.add(cancel);
1348 buttonPanel.add(loadColours);
1349 buttonPanel.add(saveColours);
1350 bigPanel.add(scrollPane, BorderLayout.CENTER);
1351 settingsPane.add(bigPanel, BorderLayout.CENTER);
1352 settingsPane.add(buttonPanel, BorderLayout.SOUTH);
1353 this.add(settingsPane);
1357 * Answers a suitable tooltip to show on the colour cell of the table
1361 * if true include 'click to edit' and similar text
1364 public static String getColorTooltip(FeatureColourI fcol,
1371 if (fcol.isSimpleColour())
1373 return withHint ? BASE_TOOLTIP : null;
1375 String description = fcol.getDescription();
1376 description = description.replaceAll("<", "<");
1377 description = description.replaceAll(">", ">");
1378 StringBuilder tt = new StringBuilder(description);
1381 tt.append("<br>").append(BASE_TOOLTIP).append("</br>");
1383 return JvSwingUtils.wrapTooltip(true, tt.toString());
1386 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol,
1389 boolean thr = false;
1390 StringBuilder tx = new StringBuilder();
1392 if (gcol.isColourByAttribute())
1394 tx.append(FeatureMatcher
1395 .toAttributeDisplayName(gcol.getAttributeName()));
1397 else if (!gcol.isColourByLabel())
1399 tx.append(MessageManager.getString("label.score"));
1402 if (gcol.isAboveThreshold())
1407 if (gcol.isBelowThreshold())
1412 if (gcol.isColourByLabel())
1418 if (!gcol.isColourByAttribute())
1426 Color newColor = gcol.getMaxColour();
1427 comp.setBackground(newColor);
1428 // System.err.println("Width is " + w / 2);
1429 Icon ficon = new FeatureIcon(gcol, comp.getBackground(), w, h, thr);
1430 comp.setIcon(ficon);
1431 // tt+="RGB value: Max (" + newColor.getRed() + ", "
1432 // + newColor.getGreen() + ", " + newColor.getBlue()
1433 // + ")\nMin (" + minCol.getRed() + ", " + minCol.getGreen()
1434 // + ", " + minCol.getBlue() + ")");
1436 comp.setHorizontalAlignment(SwingConstants.CENTER);
1437 comp.setText(tx.toString());
1440 // ///////////////////////////////////////////////////////////////////////
1441 // http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
1442 // ///////////////////////////////////////////////////////////////////////
1443 class FeatureTableModel extends AbstractTableModel
1445 private String[] columnNames = {
1446 MessageManager.getString("label.feature_type"),
1447 MessageManager.getString("action.colour"),
1448 MessageManager.getString("label.configuration"),
1449 MessageManager.getString("label.show") };
1451 private Object[][] data;
1453 FeatureTableModel(Object[][] data)
1458 public Object[][] getData()
1463 public void setData(Object[][] data)
1469 public int getColumnCount()
1471 return columnNames.length;
1474 public Object[] getRow(int row)
1480 public int getRowCount()
1486 public String getColumnName(int col)
1488 return columnNames[col];
1492 public Object getValueAt(int row, int col)
1494 return data[row][col];
1498 * Answers the class of the object in column c of the first row of the table
1501 public Class<?> getColumnClass(int c)
1503 Object v = getValueAt(0, c);
1504 return v == null ? null : v.getClass();
1508 public boolean isCellEditable(int row, int col)
1510 return col == 0 ? false : true;
1514 public void setValueAt(Object value, int row, int col)
1516 data[row][col] = value;
1517 fireTableCellUpdated(row, col);
1518 updateFeatureRenderer(data);
1523 class ColorRenderer extends JLabel implements TableCellRenderer
1525 Border unselectedBorder = null;
1527 Border selectedBorder = null;
1529 public ColorRenderer()
1531 setOpaque(true); // MUST do this for background to show up.
1532 setHorizontalTextPosition(SwingConstants.CENTER);
1533 setVerticalTextPosition(SwingConstants.CENTER);
1537 public Component getTableCellRendererComponent(JTable tbl, Object color,
1538 boolean isSelected, boolean hasFocus, int row, int column)
1540 FeatureColourI cellColour = (FeatureColourI) color;
1542 setBackground(tbl.getBackground());
1543 if (!cellColour.isSimpleColour())
1545 Rectangle cr = tbl.getCellRect(row, column, false);
1546 FeatureSettings.renderGraduatedColor(this, cellColour,
1547 (int) cr.getWidth(), (int) cr.getHeight());
1553 setBackground(cellColour.getColour());
1557 if (selectedBorder == null)
1559 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1560 tbl.getSelectionBackground());
1562 setBorder(selectedBorder);
1566 if (unselectedBorder == null)
1568 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1569 tbl.getBackground());
1571 setBorder(unselectedBorder);
1578 class FilterRenderer extends JLabel implements TableCellRenderer
1580 javax.swing.border.Border unselectedBorder = null;
1582 javax.swing.border.Border selectedBorder = null;
1584 public FilterRenderer()
1586 setOpaque(true); // MUST do this for background to show up.
1587 setHorizontalTextPosition(SwingConstants.CENTER);
1588 setVerticalTextPosition(SwingConstants.CENTER);
1592 public Component getTableCellRendererComponent(JTable tbl,
1593 Object filter, boolean isSelected, boolean hasFocus, int row,
1596 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) filter;
1598 String asText = theFilter.toString();
1599 setBackground(tbl.getBackground());
1600 this.setText(asText);
1605 if (selectedBorder == null)
1607 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1608 tbl.getSelectionBackground());
1610 setBorder(selectedBorder);
1614 if (unselectedBorder == null)
1616 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1617 tbl.getBackground());
1619 setBorder(unselectedBorder);
1627 * update comp using rendering settings from gcol
1632 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol)
1634 int w = comp.getWidth(), h = comp.getHeight();
1637 w = (int) comp.getPreferredSize().getWidth();
1638 h = (int) comp.getPreferredSize().getHeight();
1645 renderGraduatedColor(comp, gcol, w, h);
1648 @SuppressWarnings("serial")
1649 class ColorEditor extends AbstractCellEditor
1650 implements TableCellEditor, ActionListener
1652 FeatureColourI currentColor;
1654 FeatureTypeSettings chooser;
1660 protected static final String EDIT = "edit";
1662 int rowSelected = 0;
1664 public ColorEditor()
1666 // Set up the editor (from the table's point of view),
1667 // which is a button.
1668 // This button brings up the color chooser dialog,
1669 // which is the editor from the user's point of view.
1670 button = new JButton();
1671 button.setActionCommand(EDIT);
1672 button.addActionListener(this);
1673 button.setBorderPainted(false);
1677 * Handles events from the editor button, and from the colour/filters
1678 * dialog's OK button
1681 public void actionPerformed(ActionEvent e)
1683 if (button == e.getSource())
1685 if (currentColor.isSimpleColour())
1688 * simple colour chooser
1690 String ttl = MessageManager.formatMessage("label.select_colour_for", type);
1691 ColourChooserListener listener = new ColourChooserListener()
1694 public void colourSelected(Color c)
1696 currentColor = new FeatureColour(c);
1697 table.setValueAt(currentColor, rowSelected, COLOUR_COLUMN);
1698 fireEditingStopped();
1701 public void cancel()
1703 fireEditingStopped();
1706 JalviewColourChooser.showColourChooser(button, ttl, currentColor.getColour(), listener);
1711 * variable colour and filters dialog
1713 chooser = new FeatureTypeSettings(fr, type);
1714 if (!Platform.isJS())
1716 chooser.setRequestFocusEnabled(true);
1717 chooser.requestFocus();
1719 chooser.addActionListener(this);
1720 fireEditingStopped();
1726 * after OK in variable colour dialog, any changes to colour
1727 * (or filters!) are already set in FeatureRenderer, so just
1728 * update table data without triggering updateFeatureRenderer
1730 currentColor = fr.getFeatureColours().get(type);
1731 FeatureMatcherSetI currentFilter = fr.getFeatureFilter(type);
1732 if (currentFilter == null)
1734 currentFilter = new FeatureMatcherSet();
1736 Object[] data = ((FeatureTableModel) table.getModel())
1737 .getData()[rowSelected];
1738 data[COLOUR_COLUMN] = currentColor;
1739 data[FILTER_COLUMN] = currentFilter;
1740 fireEditingStopped();
1741 // SwingJS needs an explicit repaint() here,
1742 // rather than relying upon no validation having
1743 // occurred since the stopEditing call was made.
1744 // Its laying out has not been stopped by the modal frame
1751 * Override allows access to this method from anonymous inner classes
1754 protected void fireEditingStopped()
1756 super.fireEditingStopped();
1759 // Implement the one CellEditor method that AbstractCellEditor doesn't.
1761 public Object getCellEditorValue()
1763 return currentColor;
1766 // Implement the one method defined by TableCellEditor.
1768 public Component getTableCellEditorComponent(JTable theTable, Object value,
1769 boolean isSelected, int row, int column)
1771 currentColor = (FeatureColourI) value;
1772 this.rowSelected = row;
1773 type = table.getValueAt(row, TYPE_COLUMN).toString();
1774 button.setOpaque(true);
1775 button.setBackground(FeatureSettings.this.getBackground());
1776 if (!currentColor.isSimpleColour())
1778 JLabel btn = new JLabel();
1779 btn.setSize(button.getSize());
1780 FeatureSettings.renderGraduatedColor(btn, currentColor);
1781 button.setBackground(btn.getBackground());
1782 button.setIcon(btn.getIcon());
1783 button.setText(btn.getText());
1788 button.setIcon(null);
1789 button.setBackground(currentColor.getColour());
1796 * The cell editor for the Filter column. It displays the text of any filters
1797 * for the feature type in that row (in full as a tooltip, possible abbreviated
1798 * as display text). On click in the cell, opens the Feature Display Settings
1799 * dialog at the Filters tab.
1801 @SuppressWarnings("serial")
1802 class FilterEditor extends AbstractCellEditor
1803 implements TableCellEditor, ActionListener
1806 FeatureMatcherSetI currentFilter;
1814 protected static final String EDIT = "edit";
1816 int rowSelected = 0;
1818 public FilterEditor()
1820 button = new JButton();
1821 button.setActionCommand(EDIT);
1822 button.addActionListener(this);
1823 button.setBorderPainted(false);
1827 * Handles events from the editor button
1830 public void actionPerformed(ActionEvent e)
1832 if (button == e.getSource())
1834 FeatureTypeSettings chooser = new FeatureTypeSettings(fr, type);
1835 chooser.addActionListener(this);
1836 chooser.setRequestFocusEnabled(true);
1837 chooser.requestFocus();
1838 if (lastLocation != null)
1840 // todo open at its last position on screen
1841 chooser.setBounds(lastLocation.x, lastLocation.y,
1842 chooser.getWidth(), chooser.getHeight());
1845 fireEditingStopped();
1847 else if (e.getSource() instanceof Component)
1851 * after OK in variable colour dialog, any changes to filter
1852 * (or colours!) are already set in FeatureRenderer, so just
1853 * update table data without triggering updateFeatureRenderer
1855 FeatureColourI currentColor = fr.getFeatureColours().get(type);
1856 currentFilter = fr.getFeatureFilter(type);
1857 if (currentFilter == null)
1859 currentFilter = new FeatureMatcherSet();
1862 Object[] data = ((FeatureTableModel) table.getModel())
1863 .getData()[rowSelected];
1864 data[COLOUR_COLUMN] = currentColor;
1865 data[FILTER_COLUMN] = currentFilter;
1866 fireEditingStopped();
1867 // SwingJS needs an explicit repaint() here,
1868 // rather than relying upon no validation having
1869 // occurred since the stopEditing call was made.
1870 // Its laying out has not been stopped by the modal frame
1877 public Object getCellEditorValue()
1879 return currentFilter;
1883 public Component getTableCellEditorComponent(JTable theTable, Object value,
1884 boolean isSelected, int row, int column)
1886 currentFilter = (FeatureMatcherSetI) value;
1887 this.rowSelected = row;
1888 type = table.getValueAt(row, TYPE_COLUMN).toString();
1889 button.setOpaque(true);
1890 button.setBackground(FeatureSettings.this.getBackground());
1891 button.setText(currentFilter.toString());
1892 button.setIcon(null);
1898 class FeatureIcon implements Icon
1900 FeatureColourI gcol;
1904 boolean midspace = false;
1906 int width = 50, height = 20;
1908 int s1, e1; // start and end of midpoint band for thresholded symbol
1910 Color mpcolour = Color.white;
1912 FeatureIcon(FeatureColourI gfc, Color bg, int w, int h, boolean mspace)
1932 public int getIconWidth()
1938 public int getIconHeight()
1944 public void paintIcon(Component c, Graphics g, int x, int y)
1947 if (gcol.isColourByLabel())
1950 g.fillRect(0, 0, width, height);
1951 // need an icon here.
1952 g.setColor(gcol.getMaxColour());
1954 g.setFont(new Font("Verdana", Font.PLAIN, 9));
1956 // g.setFont(g.getFont().deriveFont(
1957 // AffineTransform.getScaleInstance(
1958 // width/g.getFontMetrics().stringWidth("Label"),
1959 // height/g.getFontMetrics().getHeight())));
1961 g.drawString(MessageManager.getString("label.label"), 0, 0);
1966 Color minCol = gcol.getMinColour();
1968 g.fillRect(0, 0, s1, height);
1971 g.setColor(Color.white);
1972 g.fillRect(s1, 0, e1 - s1, height);
1974 g.setColor(gcol.getMaxColour());
1975 // g.fillRect(0, e1, width - e1, height); // BH 2018
1976 g.fillRect(e1, 0, width - e1, height);