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
135 .getString("label.click_to_edit");
137 final FeatureRenderer fr;
139 public final AlignFrame af;
142 * 'original' fields hold settings to restore on Cancel
144 Object[][] originalData;
146 float originalTransparency;
148 Map<String, FeatureMatcherSetI> originalFilters;
150 final JInternalFrame frame;
152 JScrollPane scrollPane = new JScrollPane();
158 JSlider transparency = new JSlider();
161 * when true, constructor is still executing - so ignore UI events
163 protected volatile boolean inConstruction = true;
165 int selectedRow = -1;
167 boolean resettingTable = false;
170 * true when Feature Settings are updating from feature renderer
172 boolean handlingUpdate = false;
175 * holds {featureCount, totalExtent} for each feature type
177 Map<String, float[]> typeWidth = null;
184 public FeatureSettings(AlignFrame alignFrame)
186 this.af = alignFrame;
187 fr = af.getFeatureRenderer();
189 // save transparency for restore on Cancel
190 originalTransparency = fr.getTransparency();
191 int originalTransparencyAsPercent = (int) (originalTransparency * 100);
192 transparency.setMaximum(100 - originalTransparencyAsPercent);
194 originalFilters = new HashMap<>(fr.getFeatureFilters()); // shallow copy
199 } catch (Exception ex)
201 ex.printStackTrace();
207 public String getToolTipText(MouseEvent e)
210 int column = table.columnAtPoint(e.getPoint());
211 int row = table.rowAtPoint(e.getPoint());
216 tip = JvSwingUtils.wrapTooltip(true, MessageManager
217 .getString("label.feature_settings_click_drag"));
220 FeatureColourI colour = (FeatureColourI) table.getValueAt(row,
222 tip = getColorTooltip(colour, true);
225 FeatureMatcherSet o = (FeatureMatcherSet) table.getValueAt(row,
229 .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 ToolTipManager.sharedInstance().registerComponent(table);
259 table.setDefaultEditor(FeatureColour.class, new ColorEditor());
260 table.setDefaultRenderer(FeatureColour.class, new ColorRenderer());
262 table.setDefaultEditor(FeatureMatcherSet.class, new FilterEditor());
263 table.setDefaultRenderer(FeatureMatcherSet.class, new FilterRenderer());
265 TableColumn colourColumn = new TableColumn(COLOUR_COLUMN, 75,
266 new ColorRenderer(), new ColorEditor());
267 table.addColumn(colourColumn);
269 TableColumn filterColumn = new TableColumn(FILTER_COLUMN, 75,
270 new FilterRenderer(), new FilterEditor());
271 table.addColumn(filterColumn);
273 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
275 table.addMouseListener(new MouseAdapter()
278 public void mousePressed(MouseEvent evt)
280 selectedRow = table.rowAtPoint(evt.getPoint());
281 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
282 if (evt.isPopupTrigger())
284 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
285 showPopupMenu(selectedRow, type, colour, evt.getPoint());
287 else if (evt.getClickCount() == 2)
289 boolean invertSelection = evt.isAltDown();
290 boolean toggleSelection = Platform.isControlDown(evt);
291 boolean extendSelection = evt.isShiftDown();
292 fr.ap.alignFrame.avc.markColumnsContainingFeatures(
293 invertSelection, extendSelection, toggleSelection, type);
297 // isPopupTrigger fires on mouseReleased on Windows
299 public void mouseReleased(MouseEvent evt)
301 selectedRow = table.rowAtPoint(evt.getPoint());
302 if (evt.isPopupTrigger())
304 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
305 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
306 showPopupMenu(selectedRow, type, colour, evt.getPoint());
311 table.addMouseMotionListener(new MouseMotionAdapter()
314 public void mouseDragged(MouseEvent evt)
316 int newRow = table.rowAtPoint(evt.getPoint());
317 if (newRow != selectedRow && selectedRow != -1 && newRow != -1)
320 * reposition 'selectedRow' to 'newRow' (the dragged to location)
321 * this could be more than one row away for a very fast drag action
322 * so just swap it with adjacent rows until we get it there
324 Object[][] data = ((FeatureTableModel) table.getModel())
326 int direction = newRow < selectedRow ? -1 : 1;
327 for (int i = selectedRow; i != newRow; i += direction)
329 Object[] temp = data[i];
330 data[i] = data[i + direction];
331 data[i + direction] = temp;
333 updateFeatureRenderer(data);
335 selectedRow = newRow;
339 // table.setToolTipText(JvSwingUtils.wrapTooltip(true,
340 // MessageManager.getString("label.feature_settings_click_drag")));
341 scrollPane.setViewportView(table);
343 if (af.getViewport().isShowSequenceFeatures() || !fr.hasRenderOrder())
345 fr.findAllFeatures(true); // display everything!
348 discoverAllFeatureData();
349 final PropertyChangeListener change;
350 final FeatureSettings fs = this;
351 fr.addPropertyChangeListener(change = new PropertyChangeListener()
354 public void propertyChange(PropertyChangeEvent evt)
356 if (!fs.resettingTable && !fs.handlingUpdate)
358 fs.handlingUpdate = true;
360 // new groups may be added with new sequence feature types only
361 fs.handlingUpdate = false;
367 frame = new JInternalFrame();
368 frame.setContentPane(this);
369 Desktop.addInternalFrame(frame,
370 MessageManager.getString("label.sequence_feature_settings"),
371 600, Platform.isAMacAndNotJS() ? 480 : 450);
372 frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
374 frame.addInternalFrameListener(
375 new javax.swing.event.InternalFrameAdapter()
378 public void internalFrameClosed(
379 javax.swing.event.InternalFrameEvent evt)
381 fr.removePropertyChangeListener(change);
384 frame.setLayer(JLayeredPane.PALETTE_LAYER);
385 inConstruction = false;
389 * Constructs and shows a popup menu of possible actions on the selected row
397 protected void showPopupMenu(final int rowSelected, final String type,
398 final Object typeCol, final Point pt)
400 JPopupMenu men = new JPopupMenu(MessageManager
401 .formatMessage("label.settings_for_param", new String[]
403 final FeatureColourI featureColour = (FeatureColourI) typeCol;
406 * menu option to select (or deselect) variable colour
408 final JCheckBoxMenuItem variableColourCB = new JCheckBoxMenuItem(
409 MessageManager.getString("label.variable_colour"));
410 variableColourCB.setSelected(!featureColour.isSimpleColour());
411 men.add(variableColourCB);
414 * checkbox action listener doubles up as listener to OK
415 * from the variable colour / filters dialog
417 variableColourCB.addActionListener(new ActionListener()
420 public void actionPerformed(ActionEvent e)
422 if (e.getSource() == variableColourCB)
424 men.setVisible(true); // BH 2018 for JavaScript because this is a
426 men.setVisible(false); // BH 2018 for JavaScript because this is a
428 if (featureColour.isSimpleColour())
431 * toggle simple colour to variable colour - show dialog
433 FeatureTypeSettings fc = new FeatureTypeSettings(fr, type);
434 fc.addActionListener(this);
439 * toggle variable to simple colour - show colour chooser
441 String title = MessageManager
442 .formatMessage("label.select_colour_for", type);
443 ColourChooserListener listener = new ColourChooserListener()
446 public void colourSelected(Color c)
448 table.setValueAt(new FeatureColour(c), rowSelected,
451 updateFeatureRenderer(
452 ((FeatureTableModel) table.getModel()).getData(),
456 JalviewColourChooser.showColourChooser(FeatureSettings.this,
457 title, featureColour.getMaxColour(), listener);
462 if (e.getSource() instanceof FeatureTypeSettings)
465 * update after OK in feature colour dialog; the updated
466 * colour will have already been set in the FeatureRenderer
468 FeatureColourI fci = fr.getFeatureColours().get(type);
469 table.setValueAt(fci, rowSelected, COLOUR_COLUMN);
470 // BH 2018 setting a table value does not invalidate it.
471 // System.out.println("FeatureSettings is valied" +
480 JMenuItem scr = new JMenuItem(
481 MessageManager.getString("label.sort_by_score"));
483 scr.addActionListener(new ActionListener()
487 public void actionPerformed(ActionEvent e)
489 af.avc.sortAlignmentByFeatureScore(Arrays.asList(new String[]
493 JMenuItem dens = new JMenuItem(
494 MessageManager.getString("label.sort_by_density"));
495 dens.addActionListener(new ActionListener()
499 public void actionPerformed(ActionEvent e)
501 af.avc.sortAlignmentByFeatureDensity(Arrays.asList(new String[]
507 JMenuItem selCols = new JMenuItem(
508 MessageManager.getString("label.select_columns_containing"));
509 selCols.addActionListener(new ActionListener()
512 public void actionPerformed(ActionEvent arg0)
514 fr.ap.alignFrame.avc.markColumnsContainingFeatures(false, false,
518 JMenuItem clearCols = new JMenuItem(MessageManager
519 .getString("label.select_columns_not_containing"));
520 clearCols.addActionListener(new ActionListener()
523 public void actionPerformed(ActionEvent arg0)
525 fr.ap.alignFrame.avc.markColumnsContainingFeatures(true, false,
529 JMenuItem hideCols = new JMenuItem(
530 MessageManager.getString("label.hide_columns_containing"));
531 hideCols.addActionListener(new ActionListener()
534 public void actionPerformed(ActionEvent arg0)
536 fr.ap.alignFrame.hideFeatureColumns(type, true);
539 JMenuItem hideOtherCols = new JMenuItem(
540 MessageManager.getString("label.hide_columns_not_containing"));
541 hideOtherCols.addActionListener(new ActionListener()
544 public void actionPerformed(ActionEvent arg0)
546 fr.ap.alignFrame.hideFeatureColumns(type, false);
552 men.add(hideOtherCols);
553 men.show(table, pt.x, pt.y);
557 synchronized public void discoverAllFeatureData()
559 Set<String> allGroups = new HashSet<>();
560 AlignmentI alignment = af.getViewport().getAlignment();
562 for (int i = 0; i < alignment.getHeight(); i++)
564 SequenceI seq = alignment.getSequenceAt(i);
565 for (String group : seq.getFeatures().getFeatureGroups(true))
567 if (group != null && !allGroups.contains(group))
569 allGroups.add(group);
570 checkGroupState(group);
581 * Synchronise gui group list and check visibility of group
584 * @return true if group is visible
586 private boolean checkGroupState(String group)
588 boolean visible = fr.checkGroupVisibility(group, true);
590 for (int g = 0; g < groupPanel.getComponentCount(); g++)
592 if (((JCheckBox) groupPanel.getComponent(g)).getText().equals(group))
594 ((JCheckBox) groupPanel.getComponent(g)).setSelected(visible);
599 final String grp = group;
600 final JCheckBox check = new JCheckBox(group, visible);
601 check.setFont(new Font("Serif", Font.BOLD, 12));
602 check.setToolTipText(group);
603 check.addItemListener(new ItemListener()
606 public void itemStateChanged(ItemEvent evt)
608 fr.setGroupVisibility(check.getText(), check.isSelected());
609 resetTable(new String[] { grp });
610 af.alignPanel.paintAlignment(true, true);
613 groupPanel.add(check);
617 synchronized void resetTable(String[] groupChanged)
623 resettingTable = true;
624 typeWidth = new Hashtable<>();
625 // TODO: change avWidth calculation to 'per-sequence' average and use long
628 Set<String> displayableTypes = new HashSet<>();
629 Set<String> foundGroups = new HashSet<>();
632 * determine which feature types may be visible depending on
633 * which groups are selected, and recompute average width data
635 for (int i = 0; i < af.getViewport().getAlignment().getHeight(); i++)
638 SequenceI seq = af.getViewport().getAlignment().getSequenceAt(i);
641 * get the sequence's groups for positional features
642 * and keep track of which groups are visible
644 Set<String> groups = seq.getFeatures().getFeatureGroups(true);
645 Set<String> visibleGroups = new HashSet<>();
646 for (String group : groups)
648 if (group == null || checkGroupState(group))
650 visibleGroups.add(group);
653 foundGroups.addAll(groups);
656 * get distinct feature types for visible groups
657 * record distinct visible types, and their count and total length
659 Set<String> types = seq.getFeatures().getFeatureTypesForGroups(true,
660 visibleGroups.toArray(new String[visibleGroups.size()]));
661 for (String type : types)
663 displayableTypes.add(type);
664 float[] avWidth = typeWidth.get(type);
667 avWidth = new float[2];
668 typeWidth.put(type, avWidth);
670 // todo this could include features with a non-visible group
671 // - do we greatly care?
672 // todo should we include non-displayable features here, and only
673 // update when features are added?
674 avWidth[0] += seq.getFeatures().getFeatureCount(true, type);
675 avWidth[1] += seq.getFeatures().getTotalFeatureLength(type);
679 Object[][] data = new Object[displayableTypes.size()][COLUMN_COUNT];
682 if (fr.hasRenderOrder())
686 fr.findAllFeatures(groupChanged != null); // prod to update
687 // colourschemes. but don't
689 // First add the checks in the previous render order,
690 // in case the window has been closed and reopened
692 List<String> frl = fr.getRenderOrder();
693 for (int ro = frl.size() - 1; ro > -1; ro--)
695 String type = frl.get(ro);
697 if (!displayableTypes.contains(type))
702 data[dataIndex][TYPE_COLUMN] = type;
703 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
704 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
705 data[dataIndex][FILTER_COLUMN] = featureFilter == null
706 ? new FeatureMatcherSet()
708 data[dataIndex][SHOW_COLUMN] = new Boolean(
709 af.getViewport().getFeaturesDisplayed().isVisible(type));
711 displayableTypes.remove(type);
716 * process any extra features belonging only to
717 * a group which was just selected
719 while (!displayableTypes.isEmpty())
721 String type = displayableTypes.iterator().next();
722 data[dataIndex][TYPE_COLUMN] = type;
724 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
725 if (data[dataIndex][COLOUR_COLUMN] == null)
727 // "Colour has been updated in another view!!"
728 fr.clearRenderOrder();
731 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
732 data[dataIndex][FILTER_COLUMN] = featureFilter == null
733 ? new FeatureMatcherSet()
735 data[dataIndex][SHOW_COLUMN] = new Boolean(true);
737 displayableTypes.remove(type);
740 if (originalData == null)
742 originalData = new Object[data.length][COLUMN_COUNT];
743 for (int i = 0; i < data.length; i++)
745 System.arraycopy(data[i], 0, originalData[i], 0, COLUMN_COUNT);
750 updateOriginalData(data);
753 table.setModel(new FeatureTableModel(data));
754 table.getColumnModel().getColumn(0).setPreferredWidth(200);
756 groupPanel.setLayout(
757 new GridLayout(fr.getFeatureGroupsSize() / 4 + 1, 4));
758 pruneGroups(foundGroups);
759 groupPanel.validate();
761 updateFeatureRenderer(data, groupChanged != null);
762 resettingTable = false;
766 * Updates 'originalData' (used for restore on Cancel) if we detect that
767 * changes have been made outwith this dialog
769 * <li>a new feature type added (and made visible)</li>
770 * <li>a feature colour changed (in the Amend Features dialog)</li>
775 protected void updateOriginalData(Object[][] foundData)
777 // todo LinkedHashMap instead of Object[][] would be nice
779 Object[][] currentData = ((FeatureTableModel) table.getModel())
781 for (Object[] row : foundData)
783 String type = (String) row[TYPE_COLUMN];
784 boolean found = false;
785 for (Object[] current : currentData)
787 if (type.equals(current[TYPE_COLUMN]))
791 * currently dependent on object equality here;
792 * really need an equals method on FeatureColour
794 if (!row[COLOUR_COLUMN].equals(current[COLOUR_COLUMN]))
797 * feature colour has changed externally - update originalData
799 for (Object[] original : originalData)
801 if (type.equals(original[TYPE_COLUMN]))
803 original[COLOUR_COLUMN] = row[COLOUR_COLUMN];
814 * new feature detected - add to original data (on top)
816 Object[][] newData = new Object[originalData.length
818 for (int i = 0; i < originalData.length; i++)
820 System.arraycopy(originalData[i], 0, newData[i + 1], 0,
824 originalData = newData;
830 * Remove from the groups panel any checkboxes for groups that are not in the
831 * foundGroups set. This enables removing a group from the display when the
832 * last feature in that group is deleted.
836 protected void pruneGroups(Set<String> foundGroups)
838 for (int g = 0; g < groupPanel.getComponentCount(); g++)
840 JCheckBox checkbox = (JCheckBox) groupPanel.getComponent(g);
841 if (!foundGroups.contains(checkbox.getText()))
843 groupPanel.remove(checkbox);
849 * reorder data based on the featureRenderers global priority list.
853 private void ensureOrder(Object[][] data)
855 boolean sort = false;
856 float[] order = new float[data.length];
857 for (int i = 0; i < order.length; i++)
859 order[i] = fr.getOrder(data[i][0].toString());
862 order[i] = fr.setOrder(data[i][0].toString(), i / order.length);
866 sort = sort || order[i - 1] > order[i];
871 jalview.util.QuickSort.sort(order, data);
876 * Offers a file chooser dialog, and then loads the feature colours and
877 * filters from file in XML format and unmarshals to Jalview feature settings
881 JalviewFileChooser chooser = new JalviewFileChooser("fc",
882 SEQUENCE_FEATURE_COLOURS);
883 chooser.setFileView(new JalviewFileView());
884 chooser.setDialogTitle(
885 MessageManager.getString("label.load_feature_colours"));
886 chooser.setToolTipText(MessageManager.getString("action.load"));
887 chooser.setResponseHandler(0, new Runnable()
892 File file = chooser.getSelectedFile();
896 chooser.showOpenDialog(this);
900 * Loads feature colours and filters from XML stored in the given file
908 InputStreamReader in = new InputStreamReader(
909 new FileInputStream(file), "UTF-8");
911 JAXBContext jc = JAXBContext
912 .newInstance("jalview.xml.binding.jalview");
913 javax.xml.bind.Unmarshaller um = jc.createUnmarshaller();
914 XMLStreamReader streamReader = XMLInputFactory.newInstance()
915 .createXMLStreamReader(in);
916 JAXBElement<JalviewUserColours> jbe = um.unmarshal(streamReader,
917 JalviewUserColours.class);
918 JalviewUserColours jucs = jbe.getValue();
920 // JalviewUserColours jucs = JalviewUserColours.unmarshal(in);
923 * load feature colours
925 for (int i = jucs.getColour().size() - 1; i >= 0; i--)
927 Colour newcol = jucs.getColour().get(i);
928 FeatureColourI colour = jalview.project.Jalview2XML
929 .parseColour(newcol);
930 fr.setColour(newcol.getName(), colour);
931 fr.setOrder(newcol.getName(), i / (float) jucs.getColour().size());
935 * load feature filters; loaded filters will replace any that are
936 * currently defined, other defined filters are left unchanged
938 for (int i = 0; i < jucs.getFilter().size(); i++)
940 Filter filterModel = jucs.getFilter().get(i);
941 String featureType = filterModel.getFeatureType();
942 FeatureMatcherSetI filter = jalview.project.Jalview2XML
943 .parseFilter(featureType, filterModel.getMatcherSet());
944 if (!filter.isEmpty())
946 fr.setFeatureFilter(featureType, filter);
951 * update feature settings table
956 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
958 updateFeatureRenderer(data, false);
961 } catch (Exception ex)
963 System.out.println("Error loading User Colour File\n" + ex);
968 * Offers a file chooser dialog, and then saves the current feature colours
969 * and any filters to the selected file in XML format
973 JalviewFileChooser chooser = new JalviewFileChooser("fc",
974 SEQUENCE_FEATURE_COLOURS);
975 chooser.setFileView(new JalviewFileView());
976 chooser.setDialogTitle(
977 MessageManager.getString("label.save_feature_colours"));
978 chooser.setToolTipText(MessageManager.getString("action.save"));
979 int option = chooser.showSaveDialog(this);
980 if (option == JalviewFileChooser.APPROVE_OPTION)
982 File file = chooser.getSelectedFile();
988 * Saves feature colours and filters to the given file
994 JalviewUserColours ucs = new JalviewUserColours();
995 ucs.setSchemeName("Sequence Features");
998 PrintWriter out = new PrintWriter(
999 new OutputStreamWriter(new FileOutputStream(file), "UTF-8"));
1002 * sort feature types by colour order, from 0 (highest)
1005 Set<String> fr_colours = fr.getAllFeatureColours();
1006 String[] sortedTypes = fr_colours
1007 .toArray(new String[fr_colours.size()]);
1008 Arrays.sort(sortedTypes, new Comparator<String>()
1011 public int compare(String type1, String type2)
1013 return Float.compare(fr.getOrder(type1), fr.getOrder(type2));
1018 * save feature colours
1020 for (String featureType : sortedTypes)
1022 FeatureColourI fcol = fr.getFeatureStyle(featureType);
1023 Colour col = jalview.project.Jalview2XML.marshalColour(featureType,
1025 ucs.getColour().add(col);
1029 * save any feature filters
1031 for (String featureType : sortedTypes)
1033 FeatureMatcherSetI filter = fr.getFeatureFilter(featureType);
1034 if (filter != null && !filter.isEmpty())
1036 Iterator<FeatureMatcherI> iterator = filter.getMatchers()
1038 FeatureMatcherI firstMatcher = iterator.next();
1039 jalview.xml.binding.jalview.FeatureMatcherSet ms = jalview.project.Jalview2XML
1040 .marshalFilter(firstMatcher, iterator, filter.isAnded());
1041 Filter filterModel = new Filter();
1042 filterModel.setFeatureType(featureType);
1043 filterModel.setMatcherSet(ms);
1044 ucs.getFilter().add(filterModel);
1047 JAXBContext jaxbContext = JAXBContext
1048 .newInstance(JalviewUserColours.class);
1049 Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
1050 jaxbMarshaller.marshal(
1051 new ObjectFactory().createJalviewUserColours(ucs), out);
1053 // jaxbMarshaller.marshal(object, pout);
1054 // marshaller.marshal(object);
1057 // ucs.marshal(out);
1059 } catch (Exception ex)
1061 ex.printStackTrace();
1065 public void invertSelection()
1067 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1068 for (int i = 0; i < data.length; i++)
1070 data[i][SHOW_COLUMN] = !(Boolean) data[i][SHOW_COLUMN];
1072 updateFeatureRenderer(data, true);
1076 public void orderByAvWidth()
1078 if (table == null || table.getModel() == null)
1082 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1083 float[] width = new float[data.length];
1087 for (int i = 0; i < data.length; i++)
1089 awidth = typeWidth.get(data[i][TYPE_COLUMN]);
1092 width[i] = awidth[1] / awidth[0];// *awidth[0]*awidth[2]; - better
1093 // weight - but have to make per
1094 // sequence, too (awidth[2])
1095 // if (width[i]==1) // hack to distinguish single width sequences.
1106 boolean sort = false;
1107 for (int i = 0; i < width.length; i++)
1109 // awidth = (float[]) typeWidth.get(data[i][0]);
1112 width[i] = fr.getOrder(data[i][TYPE_COLUMN].toString());
1115 width[i] = fr.setOrder(data[i][TYPE_COLUMN].toString(),
1121 width[i] /= max; // normalize
1122 fr.setOrder(data[i][TYPE_COLUMN].toString(), width[i]); // store for
1127 sort = sort || width[i - 1] > width[i];
1132 jalview.util.QuickSort.sort(width, data);
1133 // update global priority order
1136 updateFeatureRenderer(data, false);
1144 frame.setClosed(true);
1145 } catch (Exception exe)
1151 public void updateFeatureRenderer(Object[][] data)
1153 updateFeatureRenderer(data, true);
1157 * Update the priority order of features; only repaint if this changed the
1158 * order of visible features
1163 void updateFeatureRenderer(Object[][] data, boolean visibleNew)
1165 FeatureSettingsBean[] rowData = getTableAsBeans(data);
1167 if (fr.setFeaturePriority(rowData, visibleNew))
1169 af.alignPanel.paintAlignment(true, true);
1174 * Converts table data into an array of data beans
1176 private FeatureSettingsBean[] getTableAsBeans(Object[][] data)
1178 FeatureSettingsBean[] rowData = new FeatureSettingsBean[data.length];
1179 for (int i = 0; i < data.length; i++)
1181 String type = (String) data[i][TYPE_COLUMN];
1182 FeatureColourI colour = (FeatureColourI) data[i][COLOUR_COLUMN];
1183 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) data[i][FILTER_COLUMN];
1184 Boolean isShown = (Boolean) data[i][SHOW_COLUMN];
1185 rowData[i] = new FeatureSettingsBean(type, colour, theFilter,
1191 private void jbInit() throws Exception
1193 this.setLayout(new BorderLayout());
1195 JPanel settingsPane = new JPanel();
1196 settingsPane.setLayout(new BorderLayout());
1198 JPanel bigPanel = new JPanel();
1199 bigPanel.setLayout(new BorderLayout());
1201 groupPanel = new JPanel();
1202 bigPanel.add(groupPanel, BorderLayout.NORTH);
1204 JButton invert = new JButton(
1205 MessageManager.getString("label.invert_selection"));
1206 invert.setFont(JvSwingUtils.getLabelFont());
1207 invert.addActionListener(new ActionListener()
1210 public void actionPerformed(ActionEvent e)
1216 JButton optimizeOrder = new JButton(
1217 MessageManager.getString("label.optimise_order"));
1218 optimizeOrder.setFont(JvSwingUtils.getLabelFont());
1219 optimizeOrder.addActionListener(new ActionListener()
1222 public void actionPerformed(ActionEvent e)
1228 JButton sortByScore = new JButton(
1229 MessageManager.getString("label.seq_sort_by_score"));
1230 sortByScore.setFont(JvSwingUtils.getLabelFont());
1231 sortByScore.addActionListener(new ActionListener()
1234 public void actionPerformed(ActionEvent e)
1236 af.avc.sortAlignmentByFeatureScore(null);
1239 JButton sortByDens = new JButton(
1240 MessageManager.getString("label.sequence_sort_by_density"));
1241 sortByDens.setFont(JvSwingUtils.getLabelFont());
1242 sortByDens.addActionListener(new ActionListener()
1245 public void actionPerformed(ActionEvent e)
1247 af.avc.sortAlignmentByFeatureDensity(null);
1251 JButton help = new JButton(MessageManager.getString("action.help"));
1252 help.setFont(JvSwingUtils.getLabelFont());
1253 help.addActionListener(new ActionListener()
1256 public void actionPerformed(ActionEvent e)
1260 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1261 } catch (HelpSetException e1)
1263 e1.printStackTrace();
1268 JButton cancel = new JButton(MessageManager.getString("action.cancel"));
1269 cancel.setFont(JvSwingUtils.getLabelFont());
1270 cancel.addActionListener(new ActionListener()
1273 public void actionPerformed(ActionEvent e)
1275 fr.setTransparency(originalTransparency);
1276 fr.setFeatureFilters(originalFilters);
1277 updateFeatureRenderer(originalData);
1282 JButton ok = new JButton(MessageManager.getString("action.ok"));
1283 ok.setFont(JvSwingUtils.getLabelFont());
1284 ok.addActionListener(new ActionListener()
1287 public void actionPerformed(ActionEvent e)
1293 JButton loadColours = new JButton(
1294 MessageManager.getString("label.load_colours"));
1295 loadColours.setFont(JvSwingUtils.getLabelFont());
1296 loadColours.setToolTipText(
1297 MessageManager.getString("label.load_colours_tooltip"));
1298 loadColours.addActionListener(new ActionListener()
1301 public void actionPerformed(ActionEvent e)
1307 JButton saveColours = new JButton(
1308 MessageManager.getString("label.save_colours"));
1309 saveColours.setFont(JvSwingUtils.getLabelFont());
1310 saveColours.setToolTipText(
1311 MessageManager.getString("label.save_colours_tooltip"));
1312 saveColours.addActionListener(new ActionListener()
1315 public void actionPerformed(ActionEvent e)
1320 transparency.addChangeListener(new ChangeListener()
1323 public void stateChanged(ChangeEvent evt)
1325 if (!inConstruction)
1327 fr.setTransparency((100 - transparency.getValue()) / 100f);
1328 af.alignPanel.paintAlignment(true, true);
1333 transparency.setMaximum(70);
1334 transparency.setToolTipText(
1335 MessageManager.getString("label.transparency_tip"));
1337 JPanel transPanel = new JPanel(new GridLayout(1, 2));
1338 bigPanel.add(transPanel, BorderLayout.SOUTH);
1340 JPanel transbuttons = new JPanel(new GridLayout(5, 1));
1341 transbuttons.add(optimizeOrder);
1342 transbuttons.add(invert);
1343 transbuttons.add(sortByScore);
1344 transbuttons.add(sortByDens);
1345 transbuttons.add(help);
1346 transPanel.add(transparency);
1347 transPanel.add(transbuttons);
1349 JPanel buttonPanel = new JPanel();
1350 buttonPanel.add(ok);
1351 buttonPanel.add(cancel);
1352 buttonPanel.add(loadColours);
1353 buttonPanel.add(saveColours);
1354 bigPanel.add(scrollPane, BorderLayout.CENTER);
1355 settingsPane.add(bigPanel, BorderLayout.CENTER);
1356 settingsPane.add(buttonPanel, BorderLayout.SOUTH);
1357 this.add(settingsPane);
1361 * Answers a suitable tooltip to show on the colour cell of the table
1365 * if true include 'click to edit' and similar text
1368 public static String getColorTooltip(FeatureColourI fcol,
1375 if (fcol.isSimpleColour())
1377 return withHint ? BASE_TOOLTIP : null;
1379 String description = fcol.getDescription();
1380 description = description.replaceAll("<", "<");
1381 description = description.replaceAll(">", ">");
1382 StringBuilder tt = new StringBuilder(description);
1385 tt.append("<br>").append(BASE_TOOLTIP).append("</br>");
1387 return JvSwingUtils.wrapTooltip(true, tt.toString());
1390 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol,
1393 boolean thr = false;
1394 StringBuilder tx = new StringBuilder();
1396 if (gcol.isColourByAttribute())
1398 tx.append(FeatureMatcher
1399 .toAttributeDisplayName(gcol.getAttributeName()));
1401 else if (!gcol.isColourByLabel())
1403 tx.append(MessageManager.getString("label.score"));
1406 if (gcol.isAboveThreshold())
1411 if (gcol.isBelowThreshold())
1416 if (gcol.isColourByLabel())
1422 if (!gcol.isColourByAttribute())
1430 Color newColor = gcol.getMaxColour();
1431 comp.setBackground(newColor);
1432 // System.err.println("Width is " + w / 2);
1433 Icon ficon = new FeatureIcon(gcol, comp.getBackground(), w, h, thr);
1434 comp.setIcon(ficon);
1435 // tt+="RGB value: Max (" + newColor.getRed() + ", "
1436 // + newColor.getGreen() + ", " + newColor.getBlue()
1437 // + ")\nMin (" + minCol.getRed() + ", " + minCol.getGreen()
1438 // + ", " + minCol.getBlue() + ")");
1440 comp.setHorizontalAlignment(SwingConstants.CENTER);
1441 comp.setText(tx.toString());
1444 // ///////////////////////////////////////////////////////////////////////
1445 // http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
1446 // ///////////////////////////////////////////////////////////////////////
1447 class FeatureTableModel extends AbstractTableModel
1449 private String[] columnNames = {
1450 MessageManager.getString("label.feature_type"),
1451 MessageManager.getString("action.colour"),
1452 MessageManager.getString("label.configuration"),
1453 MessageManager.getString("label.show") };
1455 private Object[][] data;
1457 FeatureTableModel(Object[][] data)
1462 public Object[][] getData()
1467 public void setData(Object[][] data)
1473 public int getColumnCount()
1475 return columnNames.length;
1478 public Object[] getRow(int row)
1484 public int getRowCount()
1490 public String getColumnName(int col)
1492 return columnNames[col];
1496 public Object getValueAt(int row, int col)
1498 return data[row][col];
1502 * Answers the class of the object in column c of the first row of the table
1505 public Class<?> getColumnClass(int c)
1507 Object v = getValueAt(0, c);
1508 return v == null ? null : v.getClass();
1512 public boolean isCellEditable(int row, int col)
1514 return col == 0 ? false : true;
1518 public void setValueAt(Object value, int row, int col)
1520 data[row][col] = value;
1521 fireTableCellUpdated(row, col);
1522 updateFeatureRenderer(data);
1527 class ColorRenderer extends JLabel implements TableCellRenderer
1529 Border unselectedBorder = null;
1531 Border selectedBorder = null;
1533 public ColorRenderer()
1535 setOpaque(true); // MUST do this for background to show up.
1536 setHorizontalTextPosition(SwingConstants.CENTER);
1537 setVerticalTextPosition(SwingConstants.CENTER);
1541 public Component getTableCellRendererComponent(JTable tbl, Object color,
1542 boolean isSelected, boolean hasFocus, int row, int column)
1544 FeatureColourI cellColour = (FeatureColourI) color;
1546 setBackground(tbl.getBackground());
1547 if (!cellColour.isSimpleColour())
1549 Rectangle cr = tbl.getCellRect(row, column, false);
1550 FeatureSettings.renderGraduatedColor(this, cellColour,
1551 (int) cr.getWidth(), (int) cr.getHeight());
1557 setBackground(cellColour.getColour());
1561 if (selectedBorder == null)
1563 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1564 tbl.getSelectionBackground());
1566 setBorder(selectedBorder);
1570 if (unselectedBorder == null)
1572 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1573 tbl.getBackground());
1575 setBorder(unselectedBorder);
1582 class FilterRenderer extends JLabel implements TableCellRenderer
1584 javax.swing.border.Border unselectedBorder = null;
1586 javax.swing.border.Border selectedBorder = null;
1588 public FilterRenderer()
1590 setOpaque(true); // MUST do this for background to show up.
1591 setHorizontalTextPosition(SwingConstants.CENTER);
1592 setVerticalTextPosition(SwingConstants.CENTER);
1596 public Component getTableCellRendererComponent(JTable tbl,
1597 Object filter, boolean isSelected, boolean hasFocus, int row,
1600 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) filter;
1602 String asText = theFilter.toString();
1603 setBackground(tbl.getBackground());
1604 this.setText(asText);
1609 if (selectedBorder == null)
1611 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1612 tbl.getSelectionBackground());
1614 setBorder(selectedBorder);
1618 if (unselectedBorder == null)
1620 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1621 tbl.getBackground());
1623 setBorder(unselectedBorder);
1631 * update comp using rendering settings from gcol
1636 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol)
1638 int w = comp.getWidth(), h = comp.getHeight();
1641 w = (int) comp.getPreferredSize().getWidth();
1642 h = (int) comp.getPreferredSize().getHeight();
1649 renderGraduatedColor(comp, gcol, w, h);
1652 @SuppressWarnings("serial")
1653 class ColorEditor extends AbstractCellEditor
1654 implements TableCellEditor, ActionListener
1656 FeatureColourI currentColor;
1658 FeatureTypeSettings chooser;
1664 protected static final String EDIT = "edit";
1666 int rowSelected = 0;
1668 public ColorEditor()
1670 // Set up the editor (from the table's point of view),
1671 // which is a button.
1672 // This button brings up the color chooser dialog,
1673 // which is the editor from the user's point of view.
1674 button = new JButton();
1675 button.setActionCommand(EDIT);
1676 button.addActionListener(this);
1677 button.setBorderPainted(false);
1681 * Handles events from the editor button, and from the colour/filters
1682 * dialog's OK button
1685 public void actionPerformed(ActionEvent e)
1687 if (button == e.getSource())
1689 if (currentColor.isSimpleColour())
1692 * simple colour chooser
1694 String ttl = MessageManager
1695 .formatMessage("label.select_colour_for", type);
1696 ColourChooserListener listener = new ColourChooserListener()
1699 public void colourSelected(Color c)
1701 currentColor = new FeatureColour(c);
1702 table.setValueAt(currentColor, rowSelected, COLOUR_COLUMN);
1703 fireEditingStopped();
1707 public void cancel()
1709 fireEditingStopped();
1712 JalviewColourChooser.showColourChooser(button, ttl,
1713 currentColor.getColour(), listener);
1718 * variable colour and filters dialog
1720 chooser = new FeatureTypeSettings(fr, type);
1721 if (!Platform.isJS())
1728 chooser.setRequestFocusEnabled(true);
1729 chooser.requestFocus();
1731 chooser.addActionListener(this);
1732 fireEditingStopped();
1738 * after OK in variable colour dialog, any changes to colour
1739 * (or filters!) are already set in FeatureRenderer, so just
1740 * update table data without triggering updateFeatureRenderer
1742 currentColor = fr.getFeatureColours().get(type);
1743 FeatureMatcherSetI currentFilter = fr.getFeatureFilter(type);
1744 if (currentFilter == null)
1746 currentFilter = new FeatureMatcherSet();
1748 Object[] data = ((FeatureTableModel) table.getModel())
1749 .getData()[rowSelected];
1750 data[COLOUR_COLUMN] = currentColor;
1751 data[FILTER_COLUMN] = currentFilter;
1752 fireEditingStopped();
1753 // SwingJS needs an explicit repaint() here,
1754 // rather than relying upon no validation having
1755 // occurred since the stopEditing call was made.
1756 // Its laying out has not been stopped by the modal frame
1763 * Override allows access to this method from anonymous inner classes
1766 protected void fireEditingStopped()
1768 super.fireEditingStopped();
1771 // Implement the one CellEditor method that AbstractCellEditor doesn't.
1773 public Object getCellEditorValue()
1775 return currentColor;
1778 // Implement the one method defined by TableCellEditor.
1780 public Component getTableCellEditorComponent(JTable theTable,
1781 Object value, boolean isSelected, int row, int column)
1783 currentColor = (FeatureColourI) value;
1784 this.rowSelected = row;
1785 type = table.getValueAt(row, TYPE_COLUMN).toString();
1786 button.setOpaque(true);
1787 button.setBackground(FeatureSettings.this.getBackground());
1788 if (!currentColor.isSimpleColour())
1790 JLabel btn = new JLabel();
1791 btn.setSize(button.getSize());
1792 FeatureSettings.renderGraduatedColor(btn, currentColor);
1793 button.setBackground(btn.getBackground());
1794 button.setIcon(btn.getIcon());
1795 button.setText(btn.getText());
1800 button.setIcon(null);
1801 button.setBackground(currentColor.getColour());
1808 * The cell editor for the Filter column. It displays the text of any filters
1809 * for the feature type in that row (in full as a tooltip, possible
1810 * abbreviated as display text). On click in the cell, opens the Feature
1811 * Display Settings dialog at the Filters tab.
1813 @SuppressWarnings("serial")
1814 class FilterEditor extends AbstractCellEditor
1815 implements TableCellEditor, ActionListener
1818 FeatureMatcherSetI currentFilter;
1826 protected static final String EDIT = "edit";
1828 int rowSelected = 0;
1830 public FilterEditor()
1832 button = new JButton();
1833 button.setActionCommand(EDIT);
1834 button.addActionListener(this);
1835 button.setBorderPainted(false);
1839 * Handles events from the editor button
1842 public void actionPerformed(ActionEvent e)
1844 if (button == e.getSource())
1846 FeatureTypeSettings chooser = new FeatureTypeSettings(fr, type);
1847 chooser.addActionListener(this);
1848 chooser.setRequestFocusEnabled(true);
1849 chooser.requestFocus();
1850 if (lastLocation != null)
1852 // todo open at its last position on screen
1853 chooser.setBounds(lastLocation.x, lastLocation.y,
1854 chooser.getWidth(), chooser.getHeight());
1857 fireEditingStopped();
1859 else if (e.getSource() instanceof Component)
1863 * after OK in variable colour dialog, any changes to filter
1864 * (or colours!) are already set in FeatureRenderer, so just
1865 * update table data without triggering updateFeatureRenderer
1867 FeatureColourI currentColor = fr.getFeatureColours().get(type);
1868 currentFilter = fr.getFeatureFilter(type);
1869 if (currentFilter == null)
1871 currentFilter = new FeatureMatcherSet();
1874 Object[] data = ((FeatureTableModel) table.getModel())
1875 .getData()[rowSelected];
1876 data[COLOUR_COLUMN] = currentColor;
1877 data[FILTER_COLUMN] = currentFilter;
1878 fireEditingStopped();
1879 // SwingJS needs an explicit repaint() here,
1880 // rather than relying upon no validation having
1881 // occurred since the stopEditing call was made.
1882 // Its laying out has not been stopped by the modal frame
1889 public Object getCellEditorValue()
1891 return currentFilter;
1895 public Component getTableCellEditorComponent(JTable theTable,
1896 Object value, boolean isSelected, int row, int column)
1898 currentFilter = (FeatureMatcherSetI) value;
1899 this.rowSelected = row;
1900 type = table.getValueAt(row, TYPE_COLUMN).toString();
1901 button.setOpaque(true);
1902 button.setBackground(FeatureSettings.this.getBackground());
1903 button.setText(currentFilter.toString());
1904 button.setIcon(null);
1910 class FeatureIcon implements Icon
1912 FeatureColourI gcol;
1916 boolean midspace = false;
1918 int width = 50, height = 20;
1920 int s1, e1; // start and end of midpoint band for thresholded symbol
1922 Color mpcolour = Color.white;
1924 FeatureIcon(FeatureColourI gfc, Color bg, int w, int h, boolean mspace)
1944 public int getIconWidth()
1950 public int getIconHeight()
1956 public void paintIcon(Component c, Graphics g, int x, int y)
1959 if (gcol.isColourByLabel())
1962 g.fillRect(0, 0, width, height);
1963 // need an icon here.
1964 g.setColor(gcol.getMaxColour());
1966 g.setFont(new Font("Verdana", Font.PLAIN, 9));
1968 // g.setFont(g.getFont().deriveFont(
1969 // AffineTransform.getScaleInstance(
1970 // width/g.getFontMetrics().stringWidth("Label"),
1971 // height/g.getFontMetrics().getHeight())));
1973 g.drawString(MessageManager.getString("label.label"), 0, 0);
1978 Color minCol = gcol.getMinColour();
1980 g.fillRect(0, 0, s1, height);
1983 g.setColor(Color.white);
1984 g.fillRect(s1, 0, e1 - s1, height);
1986 g.setColor(gcol.getMaxColour());
1987 // g.fillRect(0, e1, width - e1, height); // BH 2018
1988 g.fillRect(e1, 0, width - e1, height);