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.AlignViewportI;
24 import jalview.api.FeatureColourI;
25 import jalview.api.FeatureSettingsControllerI;
26 import jalview.api.ViewStyleI;
27 import jalview.datamodel.AlignmentI;
28 import jalview.datamodel.SequenceI;
29 import jalview.datamodel.features.FeatureMatcherI;
30 import jalview.datamodel.features.FeatureMatcherSet;
31 import jalview.datamodel.features.FeatureMatcherSetI;
32 import jalview.gui.Help.HelpId;
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.viewmodel.styles.ViewStyle;
40 import jalview.xml.binding.jalview.JalviewUserColours;
41 import jalview.xml.binding.jalview.JalviewUserColours.Colour;
42 import jalview.xml.binding.jalview.JalviewUserColours.Filter;
43 import jalview.xml.binding.jalview.ObjectFactory;
45 import java.awt.BorderLayout;
46 import java.awt.Color;
47 import java.awt.Component;
48 import java.awt.Dimension;
49 import java.awt.FlowLayout;
51 import java.awt.Graphics;
52 import java.awt.GridLayout;
53 import java.awt.Point;
54 import java.awt.Rectangle;
55 import java.awt.event.ActionEvent;
56 import java.awt.event.ActionListener;
57 import java.awt.event.ItemEvent;
58 import java.awt.event.ItemListener;
59 import java.awt.event.MouseAdapter;
60 import java.awt.event.MouseEvent;
61 import java.awt.event.MouseMotionAdapter;
62 import java.beans.PropertyChangeEvent;
63 import java.beans.PropertyChangeListener;
65 import java.io.FileInputStream;
66 import java.io.FileOutputStream;
67 import java.io.InputStreamReader;
68 import java.io.OutputStreamWriter;
69 import java.io.PrintWriter;
70 import java.util.Arrays;
71 import java.util.Comparator;
72 import java.util.HashMap;
73 import java.util.HashSet;
74 import java.util.Hashtable;
75 import java.util.Iterator;
76 import java.util.List;
80 import javax.help.HelpSetException;
81 import javax.swing.AbstractCellEditor;
82 import javax.swing.BorderFactory;
83 import javax.swing.Icon;
84 import javax.swing.JButton;
85 import javax.swing.JCheckBox;
86 import javax.swing.JColorChooser;
87 import javax.swing.JDialog;
88 import javax.swing.JInternalFrame;
89 import javax.swing.JLabel;
90 import javax.swing.JLayeredPane;
91 import javax.swing.JMenuItem;
92 import javax.swing.JPanel;
93 import javax.swing.JPopupMenu;
94 import javax.swing.JScrollPane;
95 import javax.swing.JSlider;
96 import javax.swing.JTable;
97 import javax.swing.ListSelectionModel;
98 import javax.swing.SwingConstants;
99 import javax.swing.event.ChangeEvent;
100 import javax.swing.event.ChangeListener;
101 import javax.swing.table.AbstractTableModel;
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 final FeatureRenderer fr;
136 public final AlignFrame af;
139 * 'original' fields hold settings to restore on Cancel
141 Object[][] originalData;
143 private float originalTransparency;
145 private ViewStyleI originalViewStyle;
147 private Map<String, FeatureMatcherSetI> originalFilters;
149 final JInternalFrame frame;
151 JScrollPane scrollPane = new JScrollPane();
157 JSlider transparency = new JSlider();
159 JCheckBox showComplement;
161 JCheckBox showComplementOnTop;
164 * when true, constructor is still executing - so ignore UI events
166 protected volatile boolean inConstruction = true;
168 int selectedRow = -1;
170 JButton fetchDAS = new JButton();
172 JButton saveDAS = new JButton();
174 JButton cancelDAS = new JButton();
176 boolean resettingTable = false;
179 * true when Feature Settings are updating from feature renderer
181 private boolean handlingUpdate = false;
184 * holds {featureCount, totalExtent} for each feature type
186 Map<String, float[]> typeWidth = null;
193 public FeatureSettings(AlignFrame alignFrame)
195 this.af = alignFrame;
196 fr = af.getFeatureRenderer();
198 // save transparency for restore on Cancel
199 originalTransparency = fr.getTransparency();
200 int originalTransparencyAsPercent = (int) (originalTransparency * 100);
201 transparency.setMaximum(100 - originalTransparencyAsPercent);
203 originalFilters = new HashMap<>(fr.getFeatureFilters()); // shallow copy
204 originalViewStyle = new ViewStyle(af.viewport.getViewStyle());
209 } catch (Exception ex)
211 ex.printStackTrace();
217 public String getToolTipText(MouseEvent e)
220 int column = table.columnAtPoint(e.getPoint());
224 tip = JvSwingUtils.wrapTooltip(true, MessageManager
225 .getString("label.feature_settings_click_drag"));
228 int row = table.rowAtPoint(e.getPoint());
229 FeatureMatcherSet o = (FeatureMatcherSet) table.getValueAt(row,
233 .getString("label.configure_feature_tooltip")
242 table.getTableHeader().setFont(new Font("Verdana", Font.PLAIN, 12));
243 table.setFont(new Font("Verdana", Font.PLAIN, 12));
245 // table.setDefaultRenderer(Color.class, new ColorRenderer());
246 // table.setDefaultEditor(Color.class, new ColorEditor(this));
248 table.setDefaultEditor(FeatureColour.class, new ColorEditor(this));
249 table.setDefaultRenderer(FeatureColour.class, new ColorRenderer());
251 table.setDefaultEditor(FeatureMatcherSet.class, new FilterEditor(this));
252 table.setDefaultRenderer(FeatureMatcherSet.class, new FilterRenderer());
254 TableColumn colourColumn = new TableColumn(COLOUR_COLUMN, 75,
255 new ColorRenderer(), new ColorEditor(this));
256 table.addColumn(colourColumn);
258 TableColumn filterColumn = new TableColumn(FILTER_COLUMN, 75,
259 new FilterRenderer(), new FilterEditor(this));
260 table.addColumn(filterColumn);
262 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
264 table.addMouseListener(new MouseAdapter()
267 public void mousePressed(MouseEvent evt)
269 selectedRow = table.rowAtPoint(evt.getPoint());
270 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
271 if (evt.isPopupTrigger())
273 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
274 popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
277 else if (evt.getClickCount() == 2)
279 boolean invertSelection = evt.isAltDown();
280 boolean toggleSelection = Platform.isControlDown(evt);
281 boolean extendSelection = evt.isShiftDown();
282 fr.ap.alignFrame.avc.markColumnsContainingFeatures(
283 invertSelection, extendSelection, toggleSelection, type);
287 // isPopupTrigger fires on mouseReleased on Windows
289 public void mouseReleased(MouseEvent evt)
291 selectedRow = table.rowAtPoint(evt.getPoint());
292 if (evt.isPopupTrigger())
294 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
295 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
296 popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
302 table.addMouseMotionListener(new MouseMotionAdapter()
305 public void mouseDragged(MouseEvent evt)
307 int newRow = table.rowAtPoint(evt.getPoint());
308 if (newRow != selectedRow && selectedRow != -1 && newRow != -1)
311 * reposition 'selectedRow' to 'newRow' (the dragged to location)
312 * this could be more than one row away for a very fast drag action
313 * so just swap it with adjacent rows until we get it there
315 Object[][] data = ((FeatureTableModel) table.getModel())
317 int direction = newRow < selectedRow ? -1 : 1;
318 for (int i = selectedRow; i != newRow; i += direction)
320 Object[] temp = data[i];
321 data[i] = data[i + direction];
322 data[i + direction] = temp;
324 updateFeatureRenderer(data);
326 selectedRow = newRow;
330 // table.setToolTipText(JvSwingUtils.wrapTooltip(true,
331 // MessageManager.getString("label.feature_settings_click_drag")));
332 scrollPane.setViewportView(table);
334 if (af.getViewport().isShowSequenceFeatures() || !fr.hasRenderOrder())
336 fr.findAllFeatures(true); // display everything!
339 discoverAllFeatureData();
340 final PropertyChangeListener change;
341 final FeatureSettings fs = this;
342 fr.addPropertyChangeListener(change = new PropertyChangeListener()
345 public void propertyChange(PropertyChangeEvent evt)
347 if (!fs.resettingTable && !fs.handlingUpdate)
349 fs.handlingUpdate = true;
351 // new groups may be added with new sequence feature types only
352 fs.handlingUpdate = false;
358 frame = new JInternalFrame();
359 frame.setContentPane(this);
360 if (Platform.isAMac())
362 Desktop.addInternalFrame(frame,
363 MessageManager.getString("label.sequence_feature_settings"),
368 Desktop.addInternalFrame(frame,
369 MessageManager.getString("label.sequence_feature_settings"),
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;
388 protected void popupSort(final int rowSelected, final String type,
389 final Object typeCol, final Map<String, float[][]> minmax, int x,
392 final FeatureColourI featureColour = (FeatureColourI) typeCol;
394 JPopupMenu men = new JPopupMenu(MessageManager
395 .formatMessage("label.settings_for_param", new String[]
397 JMenuItem scr = new JMenuItem(
398 MessageManager.getString("label.sort_by_score"));
400 final FeatureSettings me = this;
401 scr.addActionListener(new ActionListener()
405 public void actionPerformed(ActionEvent e)
408 .sortAlignmentByFeatureScore(Arrays.asList(new String[]
413 JMenuItem dens = new JMenuItem(
414 MessageManager.getString("label.sort_by_density"));
415 dens.addActionListener(new ActionListener()
419 public void actionPerformed(ActionEvent e)
422 .sortAlignmentByFeatureDensity(Arrays.asList(new String[]
429 JMenuItem selCols = new JMenuItem(
430 MessageManager.getString("label.select_columns_containing"));
431 selCols.addActionListener(new ActionListener()
434 public void actionPerformed(ActionEvent arg0)
436 fr.ap.alignFrame.avc.markColumnsContainingFeatures(false, false,
440 JMenuItem clearCols = new JMenuItem(MessageManager
441 .getString("label.select_columns_not_containing"));
442 clearCols.addActionListener(new ActionListener()
445 public void actionPerformed(ActionEvent arg0)
447 fr.ap.alignFrame.avc.markColumnsContainingFeatures(true, false,
451 JMenuItem hideCols = new JMenuItem(
452 MessageManager.getString("label.hide_columns_containing"));
453 hideCols.addActionListener(new ActionListener()
456 public void actionPerformed(ActionEvent arg0)
458 fr.ap.alignFrame.hideFeatureColumns(type, true);
461 JMenuItem hideOtherCols = new JMenuItem(
462 MessageManager.getString("label.hide_columns_not_containing"));
463 hideOtherCols.addActionListener(new ActionListener()
466 public void actionPerformed(ActionEvent arg0)
468 fr.ap.alignFrame.hideFeatureColumns(type, false);
474 men.add(hideOtherCols);
475 men.show(table, x, y);
479 synchronized public void discoverAllFeatureData()
481 Set<String> allGroups = new HashSet<>();
482 AlignmentI alignment = af.getViewport().getAlignment();
484 for (int i = 0; i < alignment.getHeight(); i++)
486 SequenceI seq = alignment.getSequenceAt(i);
487 for (String group : seq.getFeatures().getFeatureGroups(true))
489 if (group != null && !allGroups.contains(group))
491 allGroups.add(group);
492 checkGroupState(group);
503 * Synchronise gui group list and check visibility of group
506 * @return true if group is visible
508 private boolean checkGroupState(String group)
510 boolean visible = fr.checkGroupVisibility(group, true);
512 for (int g = 0; g < groupPanel.getComponentCount(); g++)
514 if (((JCheckBox) groupPanel.getComponent(g)).getText().equals(group))
516 ((JCheckBox) groupPanel.getComponent(g)).setSelected(visible);
521 final String grp = group;
522 final JCheckBox check = new JCheckBox(group, visible);
523 check.setFont(new Font("Serif", Font.BOLD, 12));
524 check.setToolTipText(group);
525 check.addItemListener(new ItemListener()
528 public void itemStateChanged(ItemEvent evt)
530 fr.setGroupVisibility(check.getText(), check.isSelected());
531 resetTable(new String[] { grp });
535 groupPanel.add(check);
539 synchronized void resetTable(String[] groupChanged)
545 resettingTable = true;
546 typeWidth = new Hashtable<>();
547 // TODO: change avWidth calculation to 'per-sequence' average and use long
550 Set<String> displayableTypes = new HashSet<>();
551 Set<String> foundGroups = new HashSet<>();
554 * determine which feature types may be visible depending on
555 * which groups are selected, and recompute average width data
557 for (int i = 0; i < af.getViewport().getAlignment().getHeight(); i++)
560 SequenceI seq = af.getViewport().getAlignment().getSequenceAt(i);
563 * get the sequence's groups for positional features
564 * and keep track of which groups are visible
566 Set<String> groups = seq.getFeatures().getFeatureGroups(true);
567 Set<String> visibleGroups = new HashSet<>();
568 for (String group : groups)
570 if (group == null || checkGroupState(group))
572 visibleGroups.add(group);
575 foundGroups.addAll(groups);
578 * get distinct feature types for visible groups
579 * record distinct visible types, and their count and total length
581 Set<String> types = seq.getFeatures().getFeatureTypesForGroups(true,
582 visibleGroups.toArray(new String[visibleGroups.size()]));
583 for (String type : types)
585 displayableTypes.add(type);
586 float[] avWidth = typeWidth.get(type);
589 avWidth = new float[2];
590 typeWidth.put(type, avWidth);
592 // todo this could include features with a non-visible group
593 // - do we greatly care?
594 // todo should we include non-displayable features here, and only
595 // update when features are added?
596 avWidth[0] += seq.getFeatures().getFeatureCount(true, type);
597 avWidth[1] += seq.getFeatures().getTotalFeatureLength(type);
601 Object[][] data = new Object[displayableTypes.size()][COLUMN_COUNT];
604 if (fr.hasRenderOrder())
608 fr.findAllFeatures(groupChanged != null); // prod to update
609 // colourschemes. but don't
611 // First add the checks in the previous render order,
612 // in case the window has been closed and reopened
614 List<String> frl = fr.getRenderOrder();
615 for (int ro = frl.size() - 1; ro > -1; ro--)
617 String type = frl.get(ro);
619 if (!displayableTypes.contains(type))
624 data[dataIndex][TYPE_COLUMN] = type;
625 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
626 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
627 data[dataIndex][FILTER_COLUMN] = featureFilter == null
628 ? new FeatureMatcherSet()
630 data[dataIndex][SHOW_COLUMN] = new Boolean(
631 af.getViewport().getFeaturesDisplayed().isVisible(type));
633 displayableTypes.remove(type);
638 * process any extra features belonging only to
639 * a group which was just selected
641 while (!displayableTypes.isEmpty())
643 String type = displayableTypes.iterator().next();
644 data[dataIndex][TYPE_COLUMN] = type;
646 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
647 if (data[dataIndex][COLOUR_COLUMN] == null)
649 // "Colour has been updated in another view!!"
650 fr.clearRenderOrder();
653 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
654 data[dataIndex][FILTER_COLUMN] = featureFilter == null
655 ? new FeatureMatcherSet()
657 data[dataIndex][SHOW_COLUMN] = new Boolean(true);
659 displayableTypes.remove(type);
662 if (originalData == null)
664 originalData = new Object[data.length][COLUMN_COUNT];
665 for (int i = 0; i < data.length; i++)
667 System.arraycopy(data[i], 0, originalData[i], 0, COLUMN_COUNT);
672 updateOriginalData(data);
675 table.setModel(new FeatureTableModel(data));
676 table.getColumnModel().getColumn(0).setPreferredWidth(200);
678 groupPanel.setLayout(
679 new GridLayout(fr.getFeatureGroupsSize() / 4 + 1, 4));
680 pruneGroups(foundGroups);
681 groupPanel.validate();
683 updateFeatureRenderer(data, groupChanged != null);
684 resettingTable = false;
688 * Updates 'originalData' (used for restore on Cancel) if we detect that changes
689 * have been made outwith this dialog
691 * <li>a new feature type added (and made visible)</li>
692 * <li>a feature colour changed (in the Amend Features dialog)</li>
697 protected void updateOriginalData(Object[][] foundData)
699 // todo LinkedHashMap instead of Object[][] would be nice
701 Object[][] currentData = ((FeatureTableModel) table.getModel())
703 for (Object[] row : foundData)
705 String type = (String) row[TYPE_COLUMN];
706 boolean found = false;
707 for (Object[] current : currentData)
709 if (type.equals(current[TYPE_COLUMN]))
713 * currently dependent on object equality here;
714 * really need an equals method on FeatureColour
716 if (!row[COLOUR_COLUMN].equals(current[COLOUR_COLUMN]))
719 * feature colour has changed externally - update originalData
721 for (Object[] original : originalData)
723 if (type.equals(original[TYPE_COLUMN]))
725 original[COLOUR_COLUMN] = row[COLOUR_COLUMN];
736 * new feature detected - add to original data (on top)
738 Object[][] newData = new Object[originalData.length
740 for (int i = 0; i < originalData.length; i++)
742 System.arraycopy(originalData[i], 0, newData[i + 1], 0,
746 originalData = newData;
752 * Remove from the groups panel any checkboxes for groups that are not in the
753 * foundGroups set. This enables removing a group from the display when the last
754 * feature in that group is deleted.
758 protected void pruneGroups(Set<String> foundGroups)
760 for (int g = 0; g < groupPanel.getComponentCount(); g++)
762 JCheckBox checkbox = (JCheckBox) groupPanel.getComponent(g);
763 if (!foundGroups.contains(checkbox.getText()))
765 groupPanel.remove(checkbox);
771 * reorder data based on the featureRenderers global priority list.
775 private void ensureOrder(Object[][] data)
777 boolean sort = false;
778 float[] order = new float[data.length];
779 for (int i = 0; i < order.length; i++)
781 order[i] = fr.getOrder(data[i][0].toString());
784 order[i] = fr.setOrder(data[i][0].toString(), i / order.length);
788 sort = sort || order[i - 1] > order[i];
793 jalview.util.QuickSort.sort(order, data);
798 * Offers a file chooser dialog, and then loads the feature colours and
799 * filters from file in XML format and unmarshals to Jalview feature settings
803 JalviewFileChooser chooser = new JalviewFileChooser("fc",
804 SEQUENCE_FEATURE_COLOURS);
805 chooser.setFileView(new JalviewFileView());
806 chooser.setDialogTitle(
807 MessageManager.getString("label.load_feature_colours"));
808 chooser.setToolTipText(MessageManager.getString("action.load"));
810 int value = chooser.showOpenDialog(this);
812 if (value == JalviewFileChooser.APPROVE_OPTION)
814 File file = chooser.getSelectedFile();
820 * Loads feature colours and filters from XML stored in the given file
828 InputStreamReader in = new InputStreamReader(
829 new FileInputStream(file), "UTF-8");
831 JAXBContext jc = JAXBContext
832 .newInstance("jalview.xml.binding.jalview");
833 javax.xml.bind.Unmarshaller um = jc.createUnmarshaller();
834 XMLStreamReader streamReader = XMLInputFactory.newInstance()
835 .createXMLStreamReader(in);
836 JAXBElement<JalviewUserColours> jbe = um.unmarshal(streamReader,
837 JalviewUserColours.class);
838 JalviewUserColours jucs = jbe.getValue();
840 // JalviewUserColours jucs = JalviewUserColours.unmarshal(in);
843 * load feature colours
845 for (int i = jucs.getColour().size() - 1; i >= 0; i--)
847 Colour newcol = jucs.getColour().get(i);
848 FeatureColourI colour = jalview.project.Jalview2XML
849 .parseColour(newcol);
850 fr.setColour(newcol.getName(), colour);
851 fr.setOrder(newcol.getName(), i / (float) jucs.getColour().size());
855 * load feature filters; loaded filters will replace any that are
856 * currently defined, other defined filters are left unchanged
858 for (int i = 0; i < jucs.getFilter().size(); i++)
860 Filter filterModel = jucs.getFilter().get(i);
861 String featureType = filterModel.getFeatureType();
862 FeatureMatcherSetI filter = jalview.project.Jalview2XML
863 .parseFilter(featureType, filterModel.getMatcherSet());
864 if (!filter.isEmpty())
866 fr.setFeatureFilter(featureType, filter);
871 * update feature settings table
876 Object[][] data = ((FeatureTableModel) table.getModel())
879 updateFeatureRenderer(data, false);
882 } catch (Exception ex)
884 System.out.println("Error loading User Colour File\n" + ex);
889 * Offers a file chooser dialog, and then saves the current feature colours
890 * and any filters to the selected file in XML format
894 JalviewFileChooser chooser = new JalviewFileChooser("fc",
895 SEQUENCE_FEATURE_COLOURS);
896 chooser.setFileView(new JalviewFileView());
897 chooser.setDialogTitle(
898 MessageManager.getString("label.save_feature_colours"));
899 chooser.setToolTipText(MessageManager.getString("action.save"));
901 int value = chooser.showSaveDialog(this);
903 if (value == JalviewFileChooser.APPROVE_OPTION)
905 save(chooser.getSelectedFile());
910 * Saves feature colours and filters to the given file
916 JalviewUserColours ucs = new JalviewUserColours();
917 ucs.setSchemeName("Sequence Features");
920 PrintWriter out = new PrintWriter(new OutputStreamWriter(
921 new FileOutputStream(file), "UTF-8"));
924 * sort feature types by colour order, from 0 (highest)
927 Set<String> fr_colours = fr.getAllFeatureColours();
928 String[] sortedTypes = fr_colours
929 .toArray(new String[fr_colours.size()]);
930 Arrays.sort(sortedTypes, new Comparator<String>()
933 public int compare(String type1, String type2)
935 return Float.compare(fr.getOrder(type1), fr.getOrder(type2));
940 * save feature colours
942 for (String featureType : sortedTypes)
944 FeatureColourI fcol = fr.getFeatureStyle(featureType);
945 Colour col = jalview.project.Jalview2XML.marshalColour(featureType,
947 ucs.getColour().add(col);
951 * save any feature filters
953 for (String featureType : sortedTypes)
955 FeatureMatcherSetI filter = fr.getFeatureFilter(featureType);
956 if (filter != null && !filter.isEmpty())
958 Iterator<FeatureMatcherI> iterator = filter.getMatchers().iterator();
959 FeatureMatcherI firstMatcher = iterator.next();
960 jalview.xml.binding.jalview.FeatureMatcherSet ms = jalview.project.Jalview2XML
961 .marshalFilter(firstMatcher, iterator,
963 Filter filterModel = new Filter();
964 filterModel.setFeatureType(featureType);
965 filterModel.setMatcherSet(ms);
966 ucs.getFilter().add(filterModel);
969 JAXBContext jaxbContext = JAXBContext
970 .newInstance(JalviewUserColours.class);
971 Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
972 jaxbMarshaller.marshal(
973 new ObjectFactory().createJalviewUserColours(ucs), out);
975 // jaxbMarshaller.marshal(object, pout);
976 // marshaller.marshal(object);
981 } catch (Exception ex)
983 ex.printStackTrace();
987 public void invertSelection()
989 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
990 for (int i = 0; i < data.length; i++)
992 data[i][SHOW_COLUMN] = !(Boolean) data[i][SHOW_COLUMN];
994 updateFeatureRenderer(data, true);
998 public void orderByAvWidth()
1000 if (table == null || table.getModel() == null)
1004 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1005 float[] width = new float[data.length];
1009 for (int i = 0; i < data.length; i++)
1011 awidth = typeWidth.get(data[i][TYPE_COLUMN]);
1014 width[i] = awidth[1] / awidth[0];// *awidth[0]*awidth[2]; - better
1015 // weight - but have to make per
1016 // sequence, too (awidth[2])
1017 // if (width[i]==1) // hack to distinguish single width sequences.
1028 boolean sort = false;
1029 for (int i = 0; i < width.length; i++)
1031 // awidth = (float[]) typeWidth.get(data[i][0]);
1034 width[i] = fr.getOrder(data[i][TYPE_COLUMN].toString());
1037 width[i] = fr.setOrder(data[i][TYPE_COLUMN].toString(),
1043 width[i] /= max; // normalize
1044 fr.setOrder(data[i][TYPE_COLUMN].toString(), width[i]); // store for later
1048 sort = sort || width[i - 1] > width[i];
1053 jalview.util.QuickSort.sort(width, data);
1054 // update global priority order
1057 updateFeatureRenderer(data, false);
1065 frame.setClosed(true);
1066 } catch (Exception exe)
1072 public void updateFeatureRenderer(Object[][] data)
1074 updateFeatureRenderer(data, true);
1078 * Update the priority order of features; only repaint if this changed the order
1079 * of visible features
1084 private void updateFeatureRenderer(Object[][] data, boolean visibleNew)
1086 FeatureSettingsBean[] rowData = getTableAsBeans(data);
1088 if (fr.setFeaturePriority(rowData, visibleNew))
1095 * Converts table data into an array of data beans
1097 private FeatureSettingsBean[] getTableAsBeans(Object[][] data)
1099 FeatureSettingsBean[] rowData = new FeatureSettingsBean[data.length];
1100 for (int i = 0; i < data.length; i++)
1102 String type = (String) data[i][TYPE_COLUMN];
1103 FeatureColourI colour = (FeatureColourI) data[i][COLOUR_COLUMN];
1104 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) data[i][FILTER_COLUMN];
1105 Boolean isShown = (Boolean) data[i][SHOW_COLUMN];
1106 rowData[i] = new FeatureSettingsBean(type, colour, theFilter,
1112 private void jbInit() throws Exception
1114 this.setLayout(new BorderLayout());
1116 JPanel settingsPane = new JPanel();
1117 settingsPane.setLayout(new BorderLayout());
1119 JPanel bigPanel = new JPanel();
1120 bigPanel.setLayout(new BorderLayout());
1122 groupPanel = new JPanel();
1123 bigPanel.add(groupPanel, BorderLayout.NORTH);
1125 JButton invert = new JButton(
1126 MessageManager.getString("label.invert_selection"));
1127 invert.setFont(JvSwingUtils.getLabelFont());
1128 invert.addActionListener(new ActionListener()
1131 public void actionPerformed(ActionEvent e)
1137 JButton optimizeOrder = new JButton(
1138 MessageManager.getString("label.optimise_order"));
1139 optimizeOrder.setFont(JvSwingUtils.getLabelFont());
1140 optimizeOrder.addActionListener(new ActionListener()
1143 public void actionPerformed(ActionEvent e)
1149 JButton sortByScore = new JButton(
1150 MessageManager.getString("label.seq_sort_by_score"));
1151 sortByScore.setFont(JvSwingUtils.getLabelFont());
1152 sortByScore.addActionListener(new ActionListener()
1155 public void actionPerformed(ActionEvent e)
1157 af.avc.sortAlignmentByFeatureScore(null);
1160 JButton sortByDens = new JButton(
1161 MessageManager.getString("label.sequence_sort_by_density"));
1162 sortByDens.setFont(JvSwingUtils.getLabelFont());
1163 sortByDens.addActionListener(new ActionListener()
1166 public void actionPerformed(ActionEvent e)
1168 af.avc.sortAlignmentByFeatureDensity(null);
1172 JButton help = new JButton(MessageManager.getString("action.help"));
1173 help.setFont(JvSwingUtils.getLabelFont());
1174 help.addActionListener(new ActionListener()
1177 public void actionPerformed(ActionEvent e)
1181 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1182 } catch (HelpSetException e1)
1184 e1.printStackTrace();
1188 help.setFont(JvSwingUtils.getLabelFont());
1189 help.setText(MessageManager.getString("action.help"));
1190 help.addActionListener(new ActionListener()
1193 public void actionPerformed(ActionEvent e)
1197 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1198 } catch (HelpSetException e1)
1200 e1.printStackTrace();
1205 JButton cancel = new JButton(MessageManager.getString("action.cancel"));
1206 cancel.setFont(JvSwingUtils.getLabelFont());
1207 cancel.addActionListener(new ActionListener()
1210 public void actionPerformed(ActionEvent e)
1212 fr.setTransparency(originalTransparency);
1213 fr.setFeatureFilters(originalFilters);
1214 updateFeatureRenderer(originalData);
1215 af.getViewport().setViewStyle(originalViewStyle);
1220 JButton ok = new JButton(MessageManager.getString("action.ok"));
1221 ok.setFont(JvSwingUtils.getLabelFont());
1222 ok.addActionListener(new ActionListener()
1225 public void actionPerformed(ActionEvent e)
1231 JButton loadColours = new JButton(
1232 MessageManager.getString("label.load_colours"));
1233 loadColours.setFont(JvSwingUtils.getLabelFont());
1234 loadColours.setToolTipText(
1235 MessageManager.getString("label.load_colours_tooltip"));
1236 loadColours.addActionListener(new ActionListener()
1239 public void actionPerformed(ActionEvent e)
1245 JButton saveColours = new JButton(
1246 MessageManager.getString("label.save_colours"));
1247 saveColours.setFont(JvSwingUtils.getLabelFont());
1248 saveColours.setToolTipText(
1249 MessageManager.getString("label.save_colours_tooltip"));
1250 saveColours.addActionListener(new ActionListener()
1253 public void actionPerformed(ActionEvent e)
1258 transparency.addChangeListener(new ChangeListener()
1261 public void stateChanged(ChangeEvent evt)
1263 if (!inConstruction)
1265 fr.setTransparency((100 - transparency.getValue()) / 100f);
1271 transparency.setMaximum(70);
1272 transparency.setToolTipText(
1273 MessageManager.getString("label.transparency_tip"));
1275 boolean nucleotide = af.getViewport().getAlignment().isNucleotide();
1276 showComplement = new JCheckBox(
1277 "Show " + (nucleotide ? "protein" : "CDS") + " features");
1278 showComplement.setSelected(af.getViewport().isShowComplementFeatures());
1279 showComplement.addActionListener(new ActionListener()
1282 public void actionPerformed(ActionEvent e)
1285 .setShowComplementFeatures(showComplement.isSelected());
1290 showComplementOnTop = new JCheckBox("on top");
1292 .setSelected(af.getViewport().isShowComplementFeaturesOnTop());
1293 showComplementOnTop.addActionListener(new ActionListener()
1296 public void actionPerformed(ActionEvent e)
1298 af.getViewport().setShowComplementFeaturesOnTop(
1299 showComplementOnTop.isSelected());
1304 JPanel lowerPanel = new JPanel(new GridLayout(1, 2));
1305 bigPanel.add(lowerPanel, BorderLayout.SOUTH);
1307 JPanel transbuttons = new JPanel(new GridLayout(5, 1));
1308 transbuttons.add(optimizeOrder);
1309 transbuttons.add(invert);
1310 transbuttons.add(sortByScore);
1311 transbuttons.add(sortByDens);
1312 transbuttons.add(help);
1314 boolean hasComplement = af.getViewport().getCodingComplement() != null;
1315 JPanel transPanelLeft = new JPanel(
1316 new GridLayout(hasComplement ? 3 : 2, 1));
1317 transPanelLeft.add(new JLabel(" Colour transparency" + ":"));
1318 transPanelLeft.add(transparency);
1321 JPanel cp = new JPanel(new FlowLayout(FlowLayout.LEFT));
1322 cp.add(showComplement);
1323 cp.add(showComplementOnTop);
1324 transPanelLeft.add(cp);
1326 lowerPanel.add(transPanelLeft);
1327 lowerPanel.add(transbuttons);
1329 JPanel buttonPanel = new JPanel();
1330 buttonPanel.add(ok);
1331 buttonPanel.add(cancel);
1332 buttonPanel.add(loadColours);
1333 buttonPanel.add(saveColours);
1334 bigPanel.add(scrollPane, BorderLayout.CENTER);
1335 settingsPane.add(bigPanel, BorderLayout.CENTER);
1336 settingsPane.add(buttonPanel, BorderLayout.SOUTH);
1337 this.add(settingsPane);
1341 * Repaints alignment, structure and overview (if shown). If there is a
1342 * complementary view which is showing this view's features, then also
1345 void refreshDisplay()
1347 af.alignPanel.paintAlignment(true, true);
1348 AlignViewportI complement = af.getViewport().getCodingComplement();
1349 if (complement != null && complement.isShowComplementFeatures())
1351 AlignFrame af2 = Desktop.getAlignFrameFor(complement);
1352 af2.alignPanel.paintAlignment(true, true);
1356 // ///////////////////////////////////////////////////////////////////////
1357 // http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
1358 // ///////////////////////////////////////////////////////////////////////
1359 class FeatureTableModel extends AbstractTableModel
1361 private String[] columnNames = {
1362 MessageManager.getString("label.feature_type"),
1363 MessageManager.getString("action.colour"),
1364 MessageManager.getString("label.configuration"),
1365 MessageManager.getString("label.show") };
1367 private Object[][] data;
1369 FeatureTableModel(Object[][] data)
1374 public Object[][] getData()
1379 public void setData(Object[][] data)
1385 public int getColumnCount()
1387 return columnNames.length;
1390 public Object[] getRow(int row)
1396 public int getRowCount()
1402 public String getColumnName(int col)
1404 return columnNames[col];
1408 public Object getValueAt(int row, int col)
1410 return data[row][col];
1414 * Answers the class of column c of the table
1417 public Class<?> getColumnClass(int c)
1422 return String.class;
1424 return FeatureColour.class;
1426 return FeatureMatcherSet.class;
1428 return Boolean.class;
1433 public boolean isCellEditable(int row, int col)
1435 return col == 0 ? false : true;
1439 public void setValueAt(Object value, int row, int col)
1441 data[row][col] = value;
1442 fireTableCellUpdated(row, col);
1443 updateFeatureRenderer(data);
1448 class ColorRenderer extends JLabel implements TableCellRenderer
1450 javax.swing.border.Border unselectedBorder = null;
1452 javax.swing.border.Border selectedBorder = null;
1454 final String baseTT = "Click to edit, right/apple click for menu.";
1456 public ColorRenderer()
1458 setOpaque(true); // MUST do this for background to show up.
1459 setHorizontalTextPosition(SwingConstants.CENTER);
1460 setVerticalTextPosition(SwingConstants.CENTER);
1464 public Component getTableCellRendererComponent(JTable tbl, Object color,
1465 boolean isSelected, boolean hasFocus, int row, int column)
1467 FeatureColourI cellColour = (FeatureColourI) color;
1469 setToolTipText(baseTT);
1470 setBackground(tbl.getBackground());
1471 if (!cellColour.isSimpleColour())
1473 Rectangle cr = tbl.getCellRect(row, column, false);
1474 FeatureSettings.renderGraduatedColor(this, cellColour,
1475 (int) cr.getWidth(), (int) cr.getHeight());
1481 setBackground(cellColour.getColour());
1485 if (selectedBorder == null)
1487 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1488 tbl.getSelectionBackground());
1490 setBorder(selectedBorder);
1494 if (unselectedBorder == null)
1496 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1497 tbl.getBackground());
1499 setBorder(unselectedBorder);
1506 class FilterRenderer extends JLabel implements TableCellRenderer
1508 javax.swing.border.Border unselectedBorder = null;
1510 javax.swing.border.Border selectedBorder = null;
1512 public FilterRenderer()
1514 setOpaque(true); // MUST do this for background to show up.
1515 setHorizontalTextPosition(SwingConstants.CENTER);
1516 setVerticalTextPosition(SwingConstants.CENTER);
1520 public Component getTableCellRendererComponent(JTable tbl,
1521 Object filter, boolean isSelected, boolean hasFocus, int row,
1524 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) filter;
1526 String asText = theFilter.toString();
1527 setBackground(tbl.getBackground());
1528 this.setText(asText);
1533 if (selectedBorder == null)
1535 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1536 tbl.getSelectionBackground());
1538 setBorder(selectedBorder);
1542 if (unselectedBorder == null)
1544 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1545 tbl.getBackground());
1547 setBorder(unselectedBorder);
1555 * update comp using rendering settings from gcol
1560 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol)
1562 int w = comp.getWidth(), h = comp.getHeight();
1565 w = (int) comp.getPreferredSize().getWidth();
1566 h = (int) comp.getPreferredSize().getHeight();
1573 renderGraduatedColor(comp, gcol, w, h);
1576 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol,
1579 boolean thr = false;
1580 StringBuilder tt = new StringBuilder();
1581 StringBuilder tx = new StringBuilder();
1583 if (gcol.isColourByAttribute())
1585 tx.append(String.join(":", gcol.getAttributeName()));
1587 else if (!gcol.isColourByLabel())
1589 tx.append(MessageManager.getString("label.score"));
1592 if (gcol.isAboveThreshold())
1596 tt.append("Thresholded (Above ").append(gcol.getThreshold())
1599 if (gcol.isBelowThreshold())
1603 tt.append("Thresholded (Below ").append(gcol.getThreshold())
1606 if (gcol.isColourByLabel())
1608 tt.append("Coloured by label text. ").append(tt);
1613 if (!gcol.isColourByAttribute())
1621 Color newColor = gcol.getMaxColour();
1622 comp.setBackground(newColor);
1623 // System.err.println("Width is " + w / 2);
1624 Icon ficon = new FeatureIcon(gcol, comp.getBackground(), w, h, thr);
1625 comp.setIcon(ficon);
1626 // tt+="RGB value: Max (" + newColor.getRed() + ", "
1627 // + newColor.getGreen() + ", " + newColor.getBlue()
1628 // + ")\nMin (" + minCol.getRed() + ", " + minCol.getGreen()
1629 // + ", " + minCol.getBlue() + ")");
1631 comp.setHorizontalAlignment(SwingConstants.CENTER);
1632 comp.setText(tx.toString());
1633 if (tt.length() > 0)
1635 if (comp.getToolTipText() == null)
1637 comp.setToolTipText(tt.toString());
1641 comp.setToolTipText(
1642 tt.append(" ").append(comp.getToolTipText()).toString());
1647 class ColorEditor extends AbstractCellEditor
1648 implements TableCellEditor, ActionListener
1652 FeatureColourI currentColor;
1654 FeatureTypeSettings chooser;
1660 JColorChooser colorChooser;
1664 protected static final String EDIT = "edit";
1666 int rowSelected = 0;
1668 public ColorEditor(FeatureSettings me)
1671 // Set up the editor (from the table's point of view),
1672 // which is a button.
1673 // This button brings up the color chooser dialog,
1674 // which is the editor from the user's point of view.
1675 button = new JButton();
1676 button.setActionCommand(EDIT);
1677 button.addActionListener(this);
1678 button.setBorderPainted(false);
1679 // Set up the dialog that the button brings up.
1680 colorChooser = new JColorChooser();
1681 dialog = JColorChooser.createDialog(button,
1682 MessageManager.getString("label.select_colour"), true, // modal
1683 colorChooser, this, // OK button handler
1684 null); // no CANCEL button handler
1688 * Handles events from the editor button and from the dialog's OK button.
1691 public void actionPerformed(ActionEvent e)
1693 // todo test e.getSource() instead here
1694 if (EDIT.equals(e.getActionCommand()))
1696 // The user has clicked the cell, so
1697 // bring up the dialog.
1698 if (currentColor.isSimpleColour())
1700 // bring up simple color chooser
1701 button.setBackground(currentColor.getColour());
1702 colorChooser.setColor(currentColor.getColour());
1703 dialog.setVisible(true);
1707 // bring up graduated chooser.
1708 chooser = new FeatureTypeSettings(me.fr, type);
1713 chooser.setRequestFocusEnabled(true);
1714 chooser.requestFocus();
1716 chooser.addActionListener(this);
1717 // Make the renderer reappear.
1718 fireEditingStopped();
1723 if (currentColor.isSimpleColour())
1726 * read off colour picked in colour chooser after OK pressed
1728 currentColor = new FeatureColour(colorChooser.getColor());
1729 me.table.setValueAt(currentColor, rowSelected, COLOUR_COLUMN);
1734 * after OK in variable colour dialog, any changes to colour
1735 * (or filters!) are already set in FeatureRenderer, so just
1736 * update table data without triggering updateFeatureRenderer
1738 currentColor = fr.getFeatureColours().get(type);
1739 FeatureMatcherSetI currentFilter = me.fr.getFeatureFilter(type);
1740 if (currentFilter == null)
1742 currentFilter = new FeatureMatcherSet();
1744 Object[] data = ((FeatureTableModel) table.getModel())
1745 .getData()[rowSelected];
1746 data[COLOUR_COLUMN] = currentColor;
1747 data[FILTER_COLUMN] = currentFilter;
1749 fireEditingStopped();
1750 me.table.validate();
1754 // Implement the one CellEditor method that AbstractCellEditor doesn't.
1756 public Object getCellEditorValue()
1758 return currentColor;
1761 // Implement the one method defined by TableCellEditor.
1763 public Component getTableCellEditorComponent(JTable theTable, Object value,
1764 boolean isSelected, int row, int column)
1766 currentColor = (FeatureColourI) value;
1767 this.rowSelected = row;
1768 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1769 button.setOpaque(true);
1770 button.setBackground(me.getBackground());
1771 if (!currentColor.isSimpleColour())
1773 JLabel btn = new JLabel();
1774 btn.setSize(button.getSize());
1775 FeatureSettings.renderGraduatedColor(btn, currentColor);
1776 button.setBackground(btn.getBackground());
1777 button.setIcon(btn.getIcon());
1778 button.setText(btn.getText());
1783 button.setIcon(null);
1784 button.setBackground(currentColor.getColour());
1791 * The cell editor for the Filter column. It displays the text of any filters
1792 * for the feature type in that row (in full as a tooltip, possible abbreviated
1793 * as display text). On click in the cell, opens the Feature Display Settings
1794 * dialog at the Filters tab.
1796 class FilterEditor extends AbstractCellEditor
1797 implements TableCellEditor, ActionListener
1801 FeatureMatcherSetI currentFilter;
1809 protected static final String EDIT = "edit";
1811 int rowSelected = 0;
1813 public FilterEditor(FeatureSettings me)
1816 button = new JButton();
1817 button.setActionCommand(EDIT);
1818 button.addActionListener(this);
1819 button.setBorderPainted(false);
1823 * Handles events from the editor button
1826 public void actionPerformed(ActionEvent e)
1828 if (button == e.getSource())
1830 FeatureTypeSettings chooser = new FeatureTypeSettings(me.fr, type);
1831 chooser.addActionListener(this);
1832 chooser.setRequestFocusEnabled(true);
1833 chooser.requestFocus();
1834 if (lastLocation != null)
1836 // todo open at its last position on screen
1837 chooser.setBounds(lastLocation.x, lastLocation.y,
1838 chooser.getWidth(), chooser.getHeight());
1841 fireEditingStopped();
1843 else if (e.getSource() instanceof Component)
1847 * after OK in variable colour dialog, any changes to filter
1848 * (or colours!) are already set in FeatureRenderer, so just
1849 * update table data without triggering updateFeatureRenderer
1851 FeatureColourI currentColor = fr.getFeatureColours().get(type);
1852 currentFilter = me.fr.getFeatureFilter(type);
1853 if (currentFilter == null)
1855 currentFilter = new FeatureMatcherSet();
1857 Object[] data = ((FeatureTableModel) table.getModel())
1858 .getData()[rowSelected];
1859 data[COLOUR_COLUMN] = currentColor;
1860 data[FILTER_COLUMN] = currentFilter;
1861 fireEditingStopped();
1862 me.table.validate();
1867 public Object getCellEditorValue()
1869 return currentFilter;
1873 public Component getTableCellEditorComponent(JTable theTable, Object value,
1874 boolean isSelected, int row, int column)
1876 currentFilter = (FeatureMatcherSetI) value;
1877 this.rowSelected = row;
1878 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1879 button.setOpaque(true);
1880 button.setBackground(me.getBackground());
1881 button.setText(currentFilter.toString());
1882 button.setToolTipText(currentFilter.toString());
1883 button.setIcon(null);
1889 class FeatureIcon implements Icon
1891 FeatureColourI gcol;
1895 boolean midspace = false;
1897 int width = 50, height = 20;
1899 int s1, e1; // start and end of midpoint band for thresholded symbol
1901 Color mpcolour = Color.white;
1903 FeatureIcon(FeatureColourI gfc, Color bg, int w, int h, boolean mspace)
1923 public int getIconWidth()
1929 public int getIconHeight()
1935 public void paintIcon(Component c, Graphics g, int x, int y)
1938 if (gcol.isColourByLabel())
1941 g.fillRect(0, 0, width, height);
1942 // need an icon here.
1943 g.setColor(gcol.getMaxColour());
1945 g.setFont(new Font("Verdana", Font.PLAIN, 9));
1947 // g.setFont(g.getFont().deriveFont(
1948 // AffineTransform.getScaleInstance(
1949 // width/g.getFontMetrics().stringWidth("Label"),
1950 // height/g.getFontMetrics().getHeight())));
1952 g.drawString(MessageManager.getString("label.label"), 0, 0);
1957 Color minCol = gcol.getMinColour();
1959 g.fillRect(0, 0, s1, height);
1962 g.setColor(Color.white);
1963 g.fillRect(s1, 0, e1 - s1, height);
1965 g.setColor(gcol.getMaxColour());
1966 g.fillRect(0, e1, width - e1, height);