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 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 and
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 checkbox
425 men.setVisible(false); // BH 2018 for JavaScript because this is a checkbox
426 if (featureColour.isSimpleColour())
429 * toggle simple colour to variable colour - show dialog
431 FeatureTypeSettings fc = new FeatureTypeSettings(fr, type);
432 fc.addActionListener(this);
437 * toggle variable to simple colour - show colour chooser
439 String title = MessageManager.formatMessage("label.select_colour_for", type);
440 ColourChooserListener listener = new ColourChooserListener()
443 public void colourSelected(Color c)
445 table.setValueAt(new FeatureColour(c), rowSelected,
448 updateFeatureRenderer(
449 ((FeatureTableModel) table.getModel()).getData(),
453 JalviewColourChooser.showColourChooser(FeatureSettings.this, title,
454 featureColour.getMaxColour(), listener);
459 if (e.getSource() instanceof FeatureTypeSettings)
462 * update after OK in feature colour dialog; the updated
463 * colour will have already been set in the FeatureRenderer
465 FeatureColourI fci = fr.getFeatureColours().get(type);
466 table.setValueAt(fci, rowSelected, COLOUR_COLUMN);
467 // BH 2018 setting a table value does not invalidate it.
468 // System.out.println("FeatureSettings is valied" + table.isValid());
477 JMenuItem scr = new JMenuItem(
478 MessageManager.getString("label.sort_by_score"));
480 scr.addActionListener(new ActionListener()
484 public void actionPerformed(ActionEvent e)
486 af.avc.sortAlignmentByFeatureScore(Arrays.asList(new String[]
490 JMenuItem dens = new JMenuItem(
491 MessageManager.getString("label.sort_by_density"));
492 dens.addActionListener(new ActionListener()
496 public void actionPerformed(ActionEvent e)
498 af.avc.sortAlignmentByFeatureDensity(Arrays.asList(new String[]
504 JMenuItem selCols = new JMenuItem(
505 MessageManager.getString("label.select_columns_containing"));
506 selCols.addActionListener(new ActionListener()
509 public void actionPerformed(ActionEvent arg0)
511 fr.ap.alignFrame.avc.markColumnsContainingFeatures(false, false,
515 JMenuItem clearCols = new JMenuItem(MessageManager
516 .getString("label.select_columns_not_containing"));
517 clearCols.addActionListener(new ActionListener()
520 public void actionPerformed(ActionEvent arg0)
522 fr.ap.alignFrame.avc.markColumnsContainingFeatures(true, false,
526 JMenuItem hideCols = new JMenuItem(
527 MessageManager.getString("label.hide_columns_containing"));
528 hideCols.addActionListener(new ActionListener()
531 public void actionPerformed(ActionEvent arg0)
533 fr.ap.alignFrame.hideFeatureColumns(type, true);
536 JMenuItem hideOtherCols = new JMenuItem(
537 MessageManager.getString("label.hide_columns_not_containing"));
538 hideOtherCols.addActionListener(new ActionListener()
541 public void actionPerformed(ActionEvent arg0)
543 fr.ap.alignFrame.hideFeatureColumns(type, false);
549 men.add(hideOtherCols);
550 men.show(table, pt.x, pt.y);
554 synchronized public void discoverAllFeatureData()
556 Set<String> allGroups = new HashSet<>();
557 AlignmentI alignment = af.getViewport().getAlignment();
559 for (int i = 0; i < alignment.getHeight(); i++)
561 SequenceI seq = alignment.getSequenceAt(i);
562 for (String group : seq.getFeatures().getFeatureGroups(true))
564 if (group != null && !allGroups.contains(group))
566 allGroups.add(group);
567 checkGroupState(group);
578 * Synchronise gui group list and check visibility of group
581 * @return true if group is visible
583 private boolean checkGroupState(String group)
585 boolean visible = fr.checkGroupVisibility(group, true);
587 for (int g = 0; g < groupPanel.getComponentCount(); g++)
589 if (((JCheckBox) groupPanel.getComponent(g)).getText().equals(group))
591 ((JCheckBox) groupPanel.getComponent(g)).setSelected(visible);
596 final String grp = group;
597 final JCheckBox check = new JCheckBox(group, visible);
598 check.setFont(new Font("Serif", Font.BOLD, 12));
599 check.setToolTipText(group);
600 check.addItemListener(new ItemListener()
603 public void itemStateChanged(ItemEvent evt)
605 fr.setGroupVisibility(check.getText(), check.isSelected());
606 resetTable(new String[] { grp });
607 af.alignPanel.paintAlignment(true, true);
610 groupPanel.add(check);
614 synchronized void resetTable(String[] groupChanged)
620 resettingTable = true;
621 typeWidth = new Hashtable<>();
622 // TODO: change avWidth calculation to 'per-sequence' average and use long
625 Set<String> displayableTypes = new HashSet<>();
626 Set<String> foundGroups = new HashSet<>();
629 * determine which feature types may be visible depending on
630 * which groups are selected, and recompute average width data
632 for (int i = 0; i < af.getViewport().getAlignment().getHeight(); i++)
635 SequenceI seq = af.getViewport().getAlignment().getSequenceAt(i);
638 * get the sequence's groups for positional features
639 * and keep track of which groups are visible
641 Set<String> groups = seq.getFeatures().getFeatureGroups(true);
642 Set<String> visibleGroups = new HashSet<>();
643 for (String group : groups)
645 if (group == null || checkGroupState(group))
647 visibleGroups.add(group);
650 foundGroups.addAll(groups);
653 * get distinct feature types for visible groups
654 * record distinct visible types, and their count and total length
656 Set<String> types = seq.getFeatures().getFeatureTypesForGroups(true,
657 visibleGroups.toArray(new String[visibleGroups.size()]));
658 for (String type : types)
660 displayableTypes.add(type);
661 float[] avWidth = typeWidth.get(type);
664 avWidth = new float[2];
665 typeWidth.put(type, avWidth);
667 // todo this could include features with a non-visible group
668 // - do we greatly care?
669 // todo should we include non-displayable features here, and only
670 // update when features are added?
671 avWidth[0] += seq.getFeatures().getFeatureCount(true, type);
672 avWidth[1] += seq.getFeatures().getTotalFeatureLength(type);
676 Object[][] data = new Object[displayableTypes.size()][COLUMN_COUNT];
679 if (fr.hasRenderOrder())
683 fr.findAllFeatures(groupChanged != null); // prod to update
684 // colourschemes. but don't
686 // First add the checks in the previous render order,
687 // in case the window has been closed and reopened
689 List<String> frl = fr.getRenderOrder();
690 for (int ro = frl.size() - 1; ro > -1; ro--)
692 String type = frl.get(ro);
694 if (!displayableTypes.contains(type))
699 data[dataIndex][TYPE_COLUMN] = type;
700 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
701 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
702 data[dataIndex][FILTER_COLUMN] = featureFilter == null
703 ? new FeatureMatcherSet()
705 data[dataIndex][SHOW_COLUMN] = new Boolean(
706 af.getViewport().getFeaturesDisplayed().isVisible(type));
708 displayableTypes.remove(type);
713 * process any extra features belonging only to
714 * a group which was just selected
716 while (!displayableTypes.isEmpty())
718 String type = displayableTypes.iterator().next();
719 data[dataIndex][TYPE_COLUMN] = type;
721 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
722 if (data[dataIndex][COLOUR_COLUMN] == null)
724 // "Colour has been updated in another view!!"
725 fr.clearRenderOrder();
728 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
729 data[dataIndex][FILTER_COLUMN] = featureFilter == null
730 ? new FeatureMatcherSet()
732 data[dataIndex][SHOW_COLUMN] = new Boolean(true);
734 displayableTypes.remove(type);
737 if (originalData == null)
739 originalData = new Object[data.length][COLUMN_COUNT];
740 for (int i = 0; i < data.length; i++)
742 System.arraycopy(data[i], 0, originalData[i], 0, COLUMN_COUNT);
747 updateOriginalData(data);
750 table.setModel(new FeatureTableModel(data));
751 table.getColumnModel().getColumn(0).setPreferredWidth(200);
753 groupPanel.setLayout(
754 new GridLayout(fr.getFeatureGroupsSize() / 4 + 1, 4));
755 pruneGroups(foundGroups);
756 groupPanel.validate();
758 updateFeatureRenderer(data, groupChanged != null);
759 resettingTable = false;
763 * Updates 'originalData' (used for restore on Cancel) if we detect that changes
764 * have been made outwith this dialog
766 * <li>a new feature type added (and made visible)</li>
767 * <li>a feature colour changed (in the Amend Features dialog)</li>
772 protected void updateOriginalData(Object[][] foundData)
774 // todo LinkedHashMap instead of Object[][] would be nice
776 Object[][] currentData = ((FeatureTableModel) table.getModel())
778 for (Object[] row : foundData)
780 String type = (String) row[TYPE_COLUMN];
781 boolean found = false;
782 for (Object[] current : currentData)
784 if (type.equals(current[TYPE_COLUMN]))
788 * currently dependent on object equality here;
789 * really need an equals method on FeatureColour
791 if (!row[COLOUR_COLUMN].equals(current[COLOUR_COLUMN]))
794 * feature colour has changed externally - update originalData
796 for (Object[] original : originalData)
798 if (type.equals(original[TYPE_COLUMN]))
800 original[COLOUR_COLUMN] = row[COLOUR_COLUMN];
811 * new feature detected - add to original data (on top)
813 Object[][] newData = new Object[originalData.length
815 for (int i = 0; i < originalData.length; i++)
817 System.arraycopy(originalData[i], 0, newData[i + 1], 0,
821 originalData = newData;
827 * Remove from the groups panel any checkboxes for groups that are not in the
828 * foundGroups set. This enables removing a group from the display when the last
829 * feature in that group is deleted.
833 protected void pruneGroups(Set<String> foundGroups)
835 for (int g = 0; g < groupPanel.getComponentCount(); g++)
837 JCheckBox checkbox = (JCheckBox) groupPanel.getComponent(g);
838 if (!foundGroups.contains(checkbox.getText()))
840 groupPanel.remove(checkbox);
846 * reorder data based on the featureRenderers global priority list.
850 private void ensureOrder(Object[][] data)
852 boolean sort = false;
853 float[] order = new float[data.length];
854 for (int i = 0; i < order.length; i++)
856 order[i] = fr.getOrder(data[i][0].toString());
859 order[i] = fr.setOrder(data[i][0].toString(), i / order.length);
863 sort = sort || order[i - 1] > order[i];
868 jalview.util.QuickSort.sort(order, data);
873 * Offers a file chooser dialog, and then loads the feature colours and
874 * filters from file in XML format and unmarshals to Jalview feature settings
878 JalviewFileChooser chooser = new JalviewFileChooser("fc",
879 SEQUENCE_FEATURE_COLOURS);
880 chooser.setFileView(new JalviewFileView());
881 chooser.setDialogTitle(
882 MessageManager.getString("label.load_feature_colours"));
883 chooser.setToolTipText(MessageManager.getString("action.load"));
884 chooser.setResponseHandler(0, new Runnable()
889 File file = chooser.getSelectedFile();
893 chooser.showOpenDialog(this);
897 * Loads feature colours and filters from XML stored in the given file
905 InputStreamReader in = new InputStreamReader(
906 new FileInputStream(file), "UTF-8");
908 JAXBContext jc = JAXBContext
909 .newInstance("jalview.xml.binding.jalview");
910 javax.xml.bind.Unmarshaller um = jc.createUnmarshaller();
911 XMLStreamReader streamReader = XMLInputFactory.newInstance()
912 .createXMLStreamReader(in);
913 JAXBElement<JalviewUserColours> jbe = um.unmarshal(streamReader,
914 JalviewUserColours.class);
915 JalviewUserColours jucs = jbe.getValue();
917 // JalviewUserColours jucs = JalviewUserColours.unmarshal(in);
920 * load feature colours
922 for (int i = jucs.getColour().size() - 1; i >= 0; i--)
924 Colour newcol = jucs.getColour().get(i);
925 FeatureColourI colour = jalview.project.Jalview2XML
926 .parseColour(newcol);
927 fr.setColour(newcol.getName(), colour);
928 fr.setOrder(newcol.getName(), i / (float) jucs.getColour().size());
932 * load feature filters; loaded filters will replace any that are
933 * currently defined, other defined filters are left unchanged
935 for (int i = 0; i < jucs.getFilter().size(); i++)
937 Filter filterModel = jucs.getFilter().get(i);
938 String featureType = filterModel.getFeatureType();
939 FeatureMatcherSetI filter = jalview.project.Jalview2XML
940 .parseFilter(featureType, filterModel.getMatcherSet());
941 if (!filter.isEmpty())
943 fr.setFeatureFilter(featureType, filter);
948 * update feature settings table
953 Object[][] data = ((FeatureTableModel) table.getModel())
956 updateFeatureRenderer(data, false);
959 } catch (Exception ex)
961 System.out.println("Error loading User Colour File\n" + ex);
966 * Offers a file chooser dialog, and then saves the current feature colours
967 * and any filters to the selected file in XML format
971 JalviewFileChooser chooser = new JalviewFileChooser("fc",
972 SEQUENCE_FEATURE_COLOURS);
973 chooser.setFileView(new JalviewFileView());
974 chooser.setDialogTitle(
975 MessageManager.getString("label.save_feature_colours"));
976 chooser.setToolTipText(MessageManager.getString("action.save"));
977 int option = chooser.showSaveDialog(this);
978 if (option == JalviewFileChooser.APPROVE_OPTION)
980 File file = chooser.getSelectedFile();
986 * Saves feature colours and filters to the given file
992 JalviewUserColours ucs = new JalviewUserColours();
993 ucs.setSchemeName("Sequence Features");
996 PrintWriter out = new PrintWriter(new OutputStreamWriter(
997 new FileOutputStream(file), "UTF-8"));
1000 * sort feature types by colour order, from 0 (highest)
1003 Set<String> fr_colours = fr.getAllFeatureColours();
1004 String[] sortedTypes = fr_colours
1005 .toArray(new String[fr_colours.size()]);
1006 Arrays.sort(sortedTypes, new Comparator<String>()
1009 public int compare(String type1, String type2)
1011 return Float.compare(fr.getOrder(type1), fr.getOrder(type2));
1016 * save feature colours
1018 for (String featureType : sortedTypes)
1020 FeatureColourI fcol = fr.getFeatureStyle(featureType);
1021 Colour col = jalview.project.Jalview2XML.marshalColour(featureType,
1023 ucs.getColour().add(col);
1027 * save any feature filters
1029 for (String featureType : sortedTypes)
1031 FeatureMatcherSetI filter = fr.getFeatureFilter(featureType);
1032 if (filter != null && !filter.isEmpty())
1034 Iterator<FeatureMatcherI> iterator = filter.getMatchers().iterator();
1035 FeatureMatcherI firstMatcher = iterator.next();
1036 jalview.xml.binding.jalview.FeatureMatcherSet ms = jalview.project.Jalview2XML
1037 .marshalFilter(firstMatcher, iterator,
1039 Filter filterModel = new Filter();
1040 filterModel.setFeatureType(featureType);
1041 filterModel.setMatcherSet(ms);
1042 ucs.getFilter().add(filterModel);
1045 JAXBContext jaxbContext = JAXBContext
1046 .newInstance(JalviewUserColours.class);
1047 Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
1048 jaxbMarshaller.marshal(
1049 new ObjectFactory().createJalviewUserColours(ucs), out);
1051 // jaxbMarshaller.marshal(object, pout);
1052 // marshaller.marshal(object);
1055 // ucs.marshal(out);
1057 } catch (Exception ex)
1059 ex.printStackTrace();
1063 public void invertSelection()
1065 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1066 for (int i = 0; i < data.length; i++)
1068 data[i][SHOW_COLUMN] = !(Boolean) data[i][SHOW_COLUMN];
1070 updateFeatureRenderer(data, true);
1074 public void orderByAvWidth()
1076 if (table == null || table.getModel() == null)
1080 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1081 float[] width = new float[data.length];
1085 for (int i = 0; i < data.length; i++)
1087 awidth = typeWidth.get(data[i][TYPE_COLUMN]);
1090 width[i] = awidth[1] / awidth[0];// *awidth[0]*awidth[2]; - better
1091 // weight - but have to make per
1092 // sequence, too (awidth[2])
1093 // if (width[i]==1) // hack to distinguish single width sequences.
1104 boolean sort = false;
1105 for (int i = 0; i < width.length; i++)
1107 // awidth = (float[]) typeWidth.get(data[i][0]);
1110 width[i] = fr.getOrder(data[i][TYPE_COLUMN].toString());
1113 width[i] = fr.setOrder(data[i][TYPE_COLUMN].toString(),
1119 width[i] /= max; // normalize
1120 fr.setOrder(data[i][TYPE_COLUMN].toString(), width[i]); // store for later
1124 sort = sort || width[i - 1] > width[i];
1129 jalview.util.QuickSort.sort(width, data);
1130 // update global priority order
1133 updateFeatureRenderer(data, false);
1141 frame.setClosed(true);
1142 } catch (Exception exe)
1148 public void updateFeatureRenderer(Object[][] data)
1150 updateFeatureRenderer(data, true);
1154 * Update the priority order of features; only repaint if this changed the order
1155 * of visible features
1160 void updateFeatureRenderer(Object[][] data, boolean visibleNew)
1162 FeatureSettingsBean[] rowData = getTableAsBeans(data);
1164 if (fr.setFeaturePriority(rowData, visibleNew))
1166 af.alignPanel.paintAlignment(true, true);
1171 * Converts table data into an array of data beans
1173 private FeatureSettingsBean[] getTableAsBeans(Object[][] data)
1175 FeatureSettingsBean[] rowData = new FeatureSettingsBean[data.length];
1176 for (int i = 0; i < data.length; i++)
1178 String type = (String) data[i][TYPE_COLUMN];
1179 FeatureColourI colour = (FeatureColourI) data[i][COLOUR_COLUMN];
1180 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) data[i][FILTER_COLUMN];
1181 Boolean isShown = (Boolean) data[i][SHOW_COLUMN];
1182 rowData[i] = new FeatureSettingsBean(type, colour, theFilter,
1188 private void jbInit() throws Exception
1190 this.setLayout(new BorderLayout());
1192 JPanel settingsPane = new JPanel();
1193 settingsPane.setLayout(new BorderLayout());
1195 JPanel bigPanel = new JPanel();
1196 bigPanel.setLayout(new BorderLayout());
1198 groupPanel = new JPanel();
1199 bigPanel.add(groupPanel, BorderLayout.NORTH);
1201 JButton invert = new JButton(
1202 MessageManager.getString("label.invert_selection"));
1203 invert.setFont(JvSwingUtils.getLabelFont());
1204 invert.addActionListener(new ActionListener()
1207 public void actionPerformed(ActionEvent e)
1213 JButton optimizeOrder = new JButton(
1214 MessageManager.getString("label.optimise_order"));
1215 optimizeOrder.setFont(JvSwingUtils.getLabelFont());
1216 optimizeOrder.addActionListener(new ActionListener()
1219 public void actionPerformed(ActionEvent e)
1225 JButton sortByScore = new JButton(
1226 MessageManager.getString("label.seq_sort_by_score"));
1227 sortByScore.setFont(JvSwingUtils.getLabelFont());
1228 sortByScore.addActionListener(new ActionListener()
1231 public void actionPerformed(ActionEvent e)
1233 af.avc.sortAlignmentByFeatureScore(null);
1236 JButton sortByDens = new JButton(
1237 MessageManager.getString("label.sequence_sort_by_density"));
1238 sortByDens.setFont(JvSwingUtils.getLabelFont());
1239 sortByDens.addActionListener(new ActionListener()
1242 public void actionPerformed(ActionEvent e)
1244 af.avc.sortAlignmentByFeatureDensity(null);
1248 JButton help = new JButton(MessageManager.getString("action.help"));
1249 help.setFont(JvSwingUtils.getLabelFont());
1250 help.addActionListener(new ActionListener()
1253 public void actionPerformed(ActionEvent e)
1257 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1258 } catch (HelpSetException e1)
1260 e1.printStackTrace();
1265 JButton cancel = new JButton(MessageManager.getString("action.cancel"));
1266 cancel.setFont(JvSwingUtils.getLabelFont());
1267 cancel.addActionListener(new ActionListener()
1270 public void actionPerformed(ActionEvent e)
1272 fr.setTransparency(originalTransparency);
1273 fr.setFeatureFilters(originalFilters);
1274 updateFeatureRenderer(originalData);
1279 JButton ok = new JButton(MessageManager.getString("action.ok"));
1280 ok.setFont(JvSwingUtils.getLabelFont());
1281 ok.addActionListener(new ActionListener()
1284 public void actionPerformed(ActionEvent e)
1290 JButton loadColours = new JButton(
1291 MessageManager.getString("label.load_colours"));
1292 loadColours.setFont(JvSwingUtils.getLabelFont());
1293 loadColours.setToolTipText(
1294 MessageManager.getString("label.load_colours_tooltip"));
1295 loadColours.addActionListener(new ActionListener()
1298 public void actionPerformed(ActionEvent e)
1304 JButton saveColours = new JButton(
1305 MessageManager.getString("label.save_colours"));
1306 saveColours.setFont(JvSwingUtils.getLabelFont());
1307 saveColours.setToolTipText(
1308 MessageManager.getString("label.save_colours_tooltip"));
1309 saveColours.addActionListener(new ActionListener()
1312 public void actionPerformed(ActionEvent e)
1317 transparency.addChangeListener(new ChangeListener()
1320 public void stateChanged(ChangeEvent evt)
1322 if (!inConstruction)
1324 fr.setTransparency((100 - transparency.getValue()) / 100f);
1325 af.alignPanel.paintAlignment(true, true);
1330 transparency.setMaximum(70);
1331 transparency.setToolTipText(
1332 MessageManager.getString("label.transparency_tip"));
1334 JPanel transPanel = new JPanel(new GridLayout(1, 2));
1335 bigPanel.add(transPanel, BorderLayout.SOUTH);
1337 JPanel transbuttons = new JPanel(new GridLayout(5, 1));
1338 transbuttons.add(optimizeOrder);
1339 transbuttons.add(invert);
1340 transbuttons.add(sortByScore);
1341 transbuttons.add(sortByDens);
1342 transbuttons.add(help);
1343 transPanel.add(transparency);
1344 transPanel.add(transbuttons);
1346 JPanel buttonPanel = new JPanel();
1347 buttonPanel.add(ok);
1348 buttonPanel.add(cancel);
1349 buttonPanel.add(loadColours);
1350 buttonPanel.add(saveColours);
1351 bigPanel.add(scrollPane, BorderLayout.CENTER);
1352 settingsPane.add(bigPanel, BorderLayout.CENTER);
1353 settingsPane.add(buttonPanel, BorderLayout.SOUTH);
1354 this.add(settingsPane);
1358 * Answers a suitable tooltip to show on the colour cell of the table
1362 * if true include 'click to edit' and similar text
1365 public static String getColorTooltip(FeatureColourI fcol,
1372 if (fcol.isSimpleColour())
1374 return withHint ? BASE_TOOLTIP : null;
1376 String description = fcol.getDescription();
1377 description = description.replaceAll("<", "<");
1378 description = description.replaceAll(">", ">");
1379 StringBuilder tt = new StringBuilder(description);
1382 tt.append("<br>").append(BASE_TOOLTIP).append("</br>");
1384 return JvSwingUtils.wrapTooltip(true, tt.toString());
1387 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol,
1390 boolean thr = false;
1391 StringBuilder tx = new StringBuilder();
1393 if (gcol.isColourByAttribute())
1395 tx.append(FeatureMatcher
1396 .toAttributeDisplayName(gcol.getAttributeName()));
1398 else if (!gcol.isColourByLabel())
1400 tx.append(MessageManager.getString("label.score"));
1403 if (gcol.isAboveThreshold())
1408 if (gcol.isBelowThreshold())
1413 if (gcol.isColourByLabel())
1419 if (!gcol.isColourByAttribute())
1427 Color newColor = gcol.getMaxColour();
1428 comp.setBackground(newColor);
1429 // System.err.println("Width is " + w / 2);
1430 Icon ficon = new FeatureIcon(gcol, comp.getBackground(), w, h, thr);
1431 comp.setIcon(ficon);
1432 // tt+="RGB value: Max (" + newColor.getRed() + ", "
1433 // + newColor.getGreen() + ", " + newColor.getBlue()
1434 // + ")\nMin (" + minCol.getRed() + ", " + minCol.getGreen()
1435 // + ", " + minCol.getBlue() + ")");
1437 comp.setHorizontalAlignment(SwingConstants.CENTER);
1438 comp.setText(tx.toString());
1441 // ///////////////////////////////////////////////////////////////////////
1442 // http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
1443 // ///////////////////////////////////////////////////////////////////////
1444 class FeatureTableModel extends AbstractTableModel
1446 private String[] columnNames = {
1447 MessageManager.getString("label.feature_type"),
1448 MessageManager.getString("action.colour"),
1449 MessageManager.getString("label.configuration"),
1450 MessageManager.getString("label.show") };
1452 private Object[][] data;
1454 FeatureTableModel(Object[][] data)
1459 public Object[][] getData()
1464 public void setData(Object[][] data)
1470 public int getColumnCount()
1472 return columnNames.length;
1475 public Object[] getRow(int row)
1481 public int getRowCount()
1487 public String getColumnName(int col)
1489 return columnNames[col];
1493 public Object getValueAt(int row, int col)
1495 return data[row][col];
1499 * Answers the class of the object in column c of the first row of the table
1502 public Class<?> getColumnClass(int c)
1504 Object v = getValueAt(0, c);
1505 return v == null ? null : v.getClass();
1509 public boolean isCellEditable(int row, int col)
1511 return col == 0 ? false : true;
1515 public void setValueAt(Object value, int row, int col)
1517 data[row][col] = value;
1518 fireTableCellUpdated(row, col);
1519 updateFeatureRenderer(data);
1524 class ColorRenderer extends JLabel implements TableCellRenderer
1526 Border unselectedBorder = null;
1528 Border selectedBorder = null;
1530 public ColorRenderer()
1532 setOpaque(true); // MUST do this for background to show up.
1533 setHorizontalTextPosition(SwingConstants.CENTER);
1534 setVerticalTextPosition(SwingConstants.CENTER);
1538 public Component getTableCellRendererComponent(JTable tbl, Object color,
1539 boolean isSelected, boolean hasFocus, int row, int column)
1541 FeatureColourI cellColour = (FeatureColourI) color;
1543 setBackground(tbl.getBackground());
1544 if (!cellColour.isSimpleColour())
1546 Rectangle cr = tbl.getCellRect(row, column, false);
1547 FeatureSettings.renderGraduatedColor(this, cellColour,
1548 (int) cr.getWidth(), (int) cr.getHeight());
1554 setBackground(cellColour.getColour());
1558 if (selectedBorder == null)
1560 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1561 tbl.getSelectionBackground());
1563 setBorder(selectedBorder);
1567 if (unselectedBorder == null)
1569 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1570 tbl.getBackground());
1572 setBorder(unselectedBorder);
1579 class FilterRenderer extends JLabel implements TableCellRenderer
1581 javax.swing.border.Border unselectedBorder = null;
1583 javax.swing.border.Border selectedBorder = null;
1585 public FilterRenderer()
1587 setOpaque(true); // MUST do this for background to show up.
1588 setHorizontalTextPosition(SwingConstants.CENTER);
1589 setVerticalTextPosition(SwingConstants.CENTER);
1593 public Component getTableCellRendererComponent(JTable tbl,
1594 Object filter, boolean isSelected, boolean hasFocus, int row,
1597 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) filter;
1599 String asText = theFilter.toString();
1600 setBackground(tbl.getBackground());
1601 this.setText(asText);
1606 if (selectedBorder == null)
1608 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1609 tbl.getSelectionBackground());
1611 setBorder(selectedBorder);
1615 if (unselectedBorder == null)
1617 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1618 tbl.getBackground());
1620 setBorder(unselectedBorder);
1628 * update comp using rendering settings from gcol
1633 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol)
1635 int w = comp.getWidth(), h = comp.getHeight();
1638 w = (int) comp.getPreferredSize().getWidth();
1639 h = (int) comp.getPreferredSize().getHeight();
1646 renderGraduatedColor(comp, gcol, w, h);
1649 @SuppressWarnings("serial")
1650 class ColorEditor extends AbstractCellEditor
1651 implements TableCellEditor, ActionListener
1653 FeatureColourI currentColor;
1655 FeatureTypeSettings chooser;
1661 protected static final String EDIT = "edit";
1663 int rowSelected = 0;
1665 public ColorEditor()
1667 // Set up the editor (from the table's point of view),
1668 // which is a button.
1669 // This button brings up the color chooser dialog,
1670 // which is the editor from the user's point of view.
1671 button = new JButton();
1672 button.setActionCommand(EDIT);
1673 button.addActionListener(this);
1674 button.setBorderPainted(false);
1678 * Handles events from the editor button, and from the colour/filters
1679 * dialog's OK button
1682 public void actionPerformed(ActionEvent e)
1684 if (button == e.getSource())
1686 if (currentColor.isSimpleColour())
1689 * simple colour chooser
1691 String ttl = MessageManager.formatMessage("label.select_colour_for", type);
1692 ColourChooserListener listener = new ColourChooserListener()
1695 public void colourSelected(Color c)
1697 currentColor = new FeatureColour(c);
1698 table.setValueAt(currentColor, rowSelected, COLOUR_COLUMN);
1699 fireEditingStopped();
1702 public void cancel()
1704 fireEditingStopped();
1707 JalviewColourChooser.showColourChooser(button, ttl, currentColor.getColour(), listener);
1712 * variable colour and filters dialog
1714 chooser = new FeatureTypeSettings(fr, type);
1715 if (!Platform.isJS())
1722 chooser.setRequestFocusEnabled(true);
1723 chooser.requestFocus();
1725 chooser.addActionListener(this);
1726 fireEditingStopped();
1732 * after OK in variable colour dialog, any changes to colour
1733 * (or filters!) are already set in FeatureRenderer, so just
1734 * update table data without triggering updateFeatureRenderer
1736 currentColor = fr.getFeatureColours().get(type);
1737 FeatureMatcherSetI currentFilter = fr.getFeatureFilter(type);
1738 if (currentFilter == null)
1740 currentFilter = new FeatureMatcherSet();
1742 Object[] data = ((FeatureTableModel) table.getModel())
1743 .getData()[rowSelected];
1744 data[COLOUR_COLUMN] = currentColor;
1745 data[FILTER_COLUMN] = currentFilter;
1746 fireEditingStopped();
1747 // SwingJS needs an explicit repaint() here,
1748 // rather than relying upon no validation having
1749 // occurred since the stopEditing call was made.
1750 // Its laying out has not been stopped by the modal frame
1757 * Override allows access to this method from anonymous inner classes
1760 protected void fireEditingStopped()
1762 super.fireEditingStopped();
1765 // Implement the one CellEditor method that AbstractCellEditor doesn't.
1767 public Object getCellEditorValue()
1769 return currentColor;
1772 // Implement the one method defined by TableCellEditor.
1774 public Component getTableCellEditorComponent(JTable theTable, Object value,
1775 boolean isSelected, int row, int column)
1777 currentColor = (FeatureColourI) value;
1778 this.rowSelected = row;
1779 type = table.getValueAt(row, TYPE_COLUMN).toString();
1780 button.setOpaque(true);
1781 button.setBackground(FeatureSettings.this.getBackground());
1782 if (!currentColor.isSimpleColour())
1784 JLabel btn = new JLabel();
1785 btn.setSize(button.getSize());
1786 FeatureSettings.renderGraduatedColor(btn, currentColor);
1787 button.setBackground(btn.getBackground());
1788 button.setIcon(btn.getIcon());
1789 button.setText(btn.getText());
1794 button.setIcon(null);
1795 button.setBackground(currentColor.getColour());
1802 * The cell editor for the Filter column. It displays the text of any filters
1803 * for the feature type in that row (in full as a tooltip, possible abbreviated
1804 * as display text). On click in the cell, opens the Feature Display Settings
1805 * dialog at the Filters tab.
1807 @SuppressWarnings("serial")
1808 class FilterEditor extends AbstractCellEditor
1809 implements TableCellEditor, ActionListener
1812 FeatureMatcherSetI currentFilter;
1820 protected static final String EDIT = "edit";
1822 int rowSelected = 0;
1824 public FilterEditor()
1826 button = new JButton();
1827 button.setActionCommand(EDIT);
1828 button.addActionListener(this);
1829 button.setBorderPainted(false);
1833 * Handles events from the editor button
1836 public void actionPerformed(ActionEvent e)
1838 if (button == e.getSource())
1840 FeatureTypeSettings chooser = new FeatureTypeSettings(fr, type);
1841 chooser.addActionListener(this);
1842 chooser.setRequestFocusEnabled(true);
1843 chooser.requestFocus();
1844 if (lastLocation != null)
1846 // todo open at its last position on screen
1847 chooser.setBounds(lastLocation.x, lastLocation.y,
1848 chooser.getWidth(), chooser.getHeight());
1851 fireEditingStopped();
1853 else if (e.getSource() instanceof Component)
1857 * after OK in variable colour dialog, any changes to filter
1858 * (or colours!) are already set in FeatureRenderer, so just
1859 * update table data without triggering updateFeatureRenderer
1861 FeatureColourI currentColor = fr.getFeatureColours().get(type);
1862 currentFilter = fr.getFeatureFilter(type);
1863 if (currentFilter == null)
1865 currentFilter = new FeatureMatcherSet();
1868 Object[] data = ((FeatureTableModel) table.getModel())
1869 .getData()[rowSelected];
1870 data[COLOUR_COLUMN] = currentColor;
1871 data[FILTER_COLUMN] = currentFilter;
1872 fireEditingStopped();
1873 // SwingJS needs an explicit repaint() here,
1874 // rather than relying upon no validation having
1875 // occurred since the stopEditing call was made.
1876 // Its laying out has not been stopped by the modal frame
1883 public Object getCellEditorValue()
1885 return currentFilter;
1889 public Component getTableCellEditorComponent(JTable theTable, Object value,
1890 boolean isSelected, int row, int column)
1892 currentFilter = (FeatureMatcherSetI) value;
1893 this.rowSelected = row;
1894 type = table.getValueAt(row, TYPE_COLUMN).toString();
1895 button.setOpaque(true);
1896 button.setBackground(FeatureSettings.this.getBackground());
1897 button.setText(currentFilter.toString());
1898 button.setIcon(null);
1904 class FeatureIcon implements Icon
1906 FeatureColourI gcol;
1910 boolean midspace = false;
1912 int width = 50, height = 20;
1914 int s1, e1; // start and end of midpoint band for thresholded symbol
1916 Color mpcolour = Color.white;
1918 FeatureIcon(FeatureColourI gfc, Color bg, int w, int h, boolean mspace)
1938 public int getIconWidth()
1944 public int getIconHeight()
1950 public void paintIcon(Component c, Graphics g, int x, int y)
1953 if (gcol.isColourByLabel())
1956 g.fillRect(0, 0, width, height);
1957 // need an icon here.
1958 g.setColor(gcol.getMaxColour());
1960 g.setFont(new Font("Verdana", Font.PLAIN, 9));
1962 // g.setFont(g.getFont().deriveFont(
1963 // AffineTransform.getScaleInstance(
1964 // width/g.getFontMetrics().stringWidth("Label"),
1965 // height/g.getFontMetrics().getHeight())));
1967 g.drawString(MessageManager.getString("label.label"), 0, 0);
1972 Color minCol = gcol.getMinColour();
1974 g.fillRect(0, 0, s1, height);
1977 g.setColor(Color.white);
1978 g.fillRect(s1, 0, e1 - s1, height);
1980 g.setColor(gcol.getMaxColour());
1981 // g.fillRect(0, e1, width - e1, height); // BH 2018
1982 g.fillRect(e1, 0, width - e1, height);