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.datamodel.AlignmentI;
27 import jalview.datamodel.SequenceI;
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.io.JalviewFileChooser;
33 import jalview.io.JalviewFileView;
34 import jalview.schemes.FeatureColour;
35 import jalview.util.MessageManager;
36 import jalview.util.Platform;
37 import jalview.viewmodel.seqfeatures.FeatureRendererModel.FeatureSettingsBean;
38 import jalview.xml.binding.jalview.JalviewUserColours;
39 import jalview.xml.binding.jalview.JalviewUserColours.Colour;
40 import jalview.xml.binding.jalview.JalviewUserColours.Filter;
41 import jalview.xml.binding.jalview.ObjectFactory;
43 import java.awt.BorderLayout;
44 import java.awt.Color;
45 import java.awt.Component;
46 import java.awt.Dimension;
47 import java.awt.FlowLayout;
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.JColorChooser;
85 import javax.swing.JDialog;
86 import javax.swing.JInternalFrame;
87 import javax.swing.JLabel;
88 import javax.swing.JLayeredPane;
89 import javax.swing.JMenuItem;
90 import javax.swing.JPanel;
91 import javax.swing.JPopupMenu;
92 import javax.swing.JScrollPane;
93 import javax.swing.JSlider;
94 import javax.swing.JTable;
95 import javax.swing.ListSelectionModel;
96 import javax.swing.SwingConstants;
97 import javax.swing.event.ChangeEvent;
98 import javax.swing.event.ChangeListener;
99 import javax.swing.table.AbstractTableModel;
100 import javax.swing.table.TableCellEditor;
101 import javax.swing.table.TableCellRenderer;
102 import javax.swing.table.TableColumn;
103 import javax.xml.bind.JAXBContext;
104 import javax.xml.bind.JAXBElement;
105 import javax.xml.bind.Marshaller;
106 import javax.xml.stream.XMLInputFactory;
107 import javax.xml.stream.XMLStreamReader;
109 public class FeatureSettings extends JPanel
110 implements FeatureSettingsControllerI
112 private static final String SEQUENCE_FEATURE_COLOURS = MessageManager
113 .getString("label.sequence_feature_colours");
116 * column indices of fields in Feature Settings table
118 static final int TYPE_COLUMN = 0;
120 static final int COLOUR_COLUMN = 1;
122 static final int FILTER_COLUMN = 2;
124 static final int SHOW_COLUMN = 3;
126 private static final int COLUMN_COUNT = 4;
128 private static final int MIN_WIDTH = 400;
130 private static final int MIN_HEIGHT = 400;
132 final FeatureRenderer fr;
134 public final AlignFrame af;
137 * 'original' fields hold settings to restore on Cancel
139 Object[][] originalData;
141 private float originalTransparency;
143 private Map<String, FeatureMatcherSetI> originalFilters;
145 final JInternalFrame frame;
147 JScrollPane scrollPane = new JScrollPane();
153 JSlider transparency = new JSlider();
155 JCheckBox showComplement;
157 JCheckBox showComplementOnTop;
160 * when true, constructor is still executing - so ignore UI events
162 protected volatile boolean inConstruction = true;
164 int selectedRow = -1;
166 JButton fetchDAS = new JButton();
168 JButton saveDAS = new JButton();
170 JButton cancelDAS = new JButton();
172 boolean resettingTable = false;
175 * true when Feature Settings are updating from feature renderer
177 private boolean handlingUpdate = false;
180 * holds {featureCount, totalExtent} for each feature type
182 Map<String, float[]> typeWidth = null;
189 public FeatureSettings(AlignFrame alignFrame)
191 this.af = alignFrame;
192 fr = af.getFeatureRenderer();
194 // save transparency for restore on Cancel
195 originalTransparency = fr.getTransparency();
196 int originalTransparencyAsPercent = (int) (originalTransparency * 100);
197 transparency.setMaximum(100 - originalTransparencyAsPercent);
199 originalFilters = new HashMap<>(fr.getFeatureFilters()); // shallow copy
204 } catch (Exception ex)
206 ex.printStackTrace();
212 public String getToolTipText(MouseEvent e)
215 int column = table.columnAtPoint(e.getPoint());
219 tip = JvSwingUtils.wrapTooltip(true, MessageManager
220 .getString("label.feature_settings_click_drag"));
223 int row = table.rowAtPoint(e.getPoint());
224 FeatureMatcherSet o = (FeatureMatcherSet) table.getValueAt(row,
228 .getString("label.configure_feature_tooltip")
237 table.getTableHeader().setFont(new Font("Verdana", Font.PLAIN, 12));
238 table.setFont(new Font("Verdana", Font.PLAIN, 12));
240 // table.setDefaultRenderer(Color.class, new ColorRenderer());
241 // table.setDefaultEditor(Color.class, new ColorEditor(this));
243 table.setDefaultEditor(FeatureColour.class, new ColorEditor(this));
244 table.setDefaultRenderer(FeatureColour.class, new ColorRenderer());
246 table.setDefaultEditor(FeatureMatcherSet.class, new FilterEditor(this));
247 table.setDefaultRenderer(FeatureMatcherSet.class, new FilterRenderer());
249 TableColumn colourColumn = new TableColumn(COLOUR_COLUMN, 75,
250 new ColorRenderer(), new ColorEditor(this));
251 table.addColumn(colourColumn);
253 TableColumn filterColumn = new TableColumn(FILTER_COLUMN, 75,
254 new FilterRenderer(), new FilterEditor(this));
255 table.addColumn(filterColumn);
257 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
259 table.addMouseListener(new MouseAdapter()
262 public void mousePressed(MouseEvent evt)
264 selectedRow = table.rowAtPoint(evt.getPoint());
265 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
266 if (evt.isPopupTrigger())
268 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
269 popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
272 else if (evt.getClickCount() == 2)
274 boolean invertSelection = evt.isAltDown();
275 boolean toggleSelection = Platform.isControlDown(evt);
276 boolean extendSelection = evt.isShiftDown();
277 fr.ap.alignFrame.avc.markColumnsContainingFeatures(
278 invertSelection, extendSelection, toggleSelection, type);
282 // isPopupTrigger fires on mouseReleased on Windows
284 public void mouseReleased(MouseEvent evt)
286 selectedRow = table.rowAtPoint(evt.getPoint());
287 if (evt.isPopupTrigger())
289 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
290 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
291 popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
297 table.addMouseMotionListener(new MouseMotionAdapter()
300 public void mouseDragged(MouseEvent evt)
302 int newRow = table.rowAtPoint(evt.getPoint());
303 if (newRow != selectedRow && selectedRow != -1 && newRow != -1)
306 * reposition 'selectedRow' to 'newRow' (the dragged to location)
307 * this could be more than one row away for a very fast drag action
308 * so just swap it with adjacent rows until we get it there
310 Object[][] data = ((FeatureTableModel) table.getModel())
312 int direction = newRow < selectedRow ? -1 : 1;
313 for (int i = selectedRow; i != newRow; i += direction)
315 Object[] temp = data[i];
316 data[i] = data[i + direction];
317 data[i + direction] = temp;
319 updateFeatureRenderer(data);
321 selectedRow = newRow;
325 // table.setToolTipText(JvSwingUtils.wrapTooltip(true,
326 // MessageManager.getString("label.feature_settings_click_drag")));
327 scrollPane.setViewportView(table);
329 if (af.getViewport().isShowSequenceFeatures() || !fr.hasRenderOrder())
331 fr.findAllFeatures(true); // display everything!
334 discoverAllFeatureData();
335 final PropertyChangeListener change;
336 final FeatureSettings fs = this;
337 fr.addPropertyChangeListener(change = new PropertyChangeListener()
340 public void propertyChange(PropertyChangeEvent evt)
342 if (!fs.resettingTable && !fs.handlingUpdate)
344 fs.handlingUpdate = true;
346 // new groups may be added with new sequence feature types only
347 fs.handlingUpdate = false;
353 frame = new JInternalFrame();
354 frame.setContentPane(this);
355 if (Platform.isAMac())
357 Desktop.addInternalFrame(frame,
358 MessageManager.getString("label.sequence_feature_settings"),
363 Desktop.addInternalFrame(frame,
364 MessageManager.getString("label.sequence_feature_settings"),
367 frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
369 frame.addInternalFrameListener(
370 new javax.swing.event.InternalFrameAdapter()
373 public void internalFrameClosed(
374 javax.swing.event.InternalFrameEvent evt)
376 fr.removePropertyChangeListener(change);
379 frame.setLayer(JLayeredPane.PALETTE_LAYER);
380 inConstruction = false;
383 protected void popupSort(final int rowSelected, final String type,
384 final Object typeCol, final Map<String, float[][]> minmax, int x,
387 final FeatureColourI featureColour = (FeatureColourI) typeCol;
389 JPopupMenu men = new JPopupMenu(MessageManager
390 .formatMessage("label.settings_for_param", new String[]
392 JMenuItem scr = new JMenuItem(
393 MessageManager.getString("label.sort_by_score"));
395 final FeatureSettings me = this;
396 scr.addActionListener(new ActionListener()
400 public void actionPerformed(ActionEvent e)
403 .sortAlignmentByFeatureScore(Arrays.asList(new String[]
408 JMenuItem dens = new JMenuItem(
409 MessageManager.getString("label.sort_by_density"));
410 dens.addActionListener(new ActionListener()
414 public void actionPerformed(ActionEvent e)
417 .sortAlignmentByFeatureDensity(Arrays.asList(new String[]
424 JMenuItem selCols = new JMenuItem(
425 MessageManager.getString("label.select_columns_containing"));
426 selCols.addActionListener(new ActionListener()
429 public void actionPerformed(ActionEvent arg0)
431 fr.ap.alignFrame.avc.markColumnsContainingFeatures(false, false,
435 JMenuItem clearCols = new JMenuItem(MessageManager
436 .getString("label.select_columns_not_containing"));
437 clearCols.addActionListener(new ActionListener()
440 public void actionPerformed(ActionEvent arg0)
442 fr.ap.alignFrame.avc.markColumnsContainingFeatures(true, false,
446 JMenuItem hideCols = new JMenuItem(
447 MessageManager.getString("label.hide_columns_containing"));
448 hideCols.addActionListener(new ActionListener()
451 public void actionPerformed(ActionEvent arg0)
453 fr.ap.alignFrame.hideFeatureColumns(type, true);
456 JMenuItem hideOtherCols = new JMenuItem(
457 MessageManager.getString("label.hide_columns_not_containing"));
458 hideOtherCols.addActionListener(new ActionListener()
461 public void actionPerformed(ActionEvent arg0)
463 fr.ap.alignFrame.hideFeatureColumns(type, false);
469 men.add(hideOtherCols);
470 men.show(table, x, y);
474 synchronized public void discoverAllFeatureData()
476 Set<String> allGroups = new HashSet<>();
477 AlignmentI alignment = af.getViewport().getAlignment();
479 for (int i = 0; i < alignment.getHeight(); i++)
481 SequenceI seq = alignment.getSequenceAt(i);
482 for (String group : seq.getFeatures().getFeatureGroups(true))
484 if (group != null && !allGroups.contains(group))
486 allGroups.add(group);
487 checkGroupState(group);
498 * Synchronise gui group list and check visibility of group
501 * @return true if group is visible
503 private boolean checkGroupState(String group)
505 boolean visible = fr.checkGroupVisibility(group, true);
507 for (int g = 0; g < groupPanel.getComponentCount(); g++)
509 if (((JCheckBox) groupPanel.getComponent(g)).getText().equals(group))
511 ((JCheckBox) groupPanel.getComponent(g)).setSelected(visible);
516 final String grp = group;
517 final JCheckBox check = new JCheckBox(group, visible);
518 check.setFont(new Font("Serif", Font.BOLD, 12));
519 check.setToolTipText(group);
520 check.addItemListener(new ItemListener()
523 public void itemStateChanged(ItemEvent evt)
525 fr.setGroupVisibility(check.getText(), check.isSelected());
526 resetTable(new String[] { grp });
530 groupPanel.add(check);
534 synchronized void resetTable(String[] groupChanged)
540 resettingTable = true;
541 typeWidth = new Hashtable<>();
542 // TODO: change avWidth calculation to 'per-sequence' average and use long
545 Set<String> displayableTypes = new HashSet<>();
546 Set<String> foundGroups = new HashSet<>();
549 * determine which feature types may be visible depending on
550 * which groups are selected, and recompute average width data
552 for (int i = 0; i < af.getViewport().getAlignment().getHeight(); i++)
555 SequenceI seq = af.getViewport().getAlignment().getSequenceAt(i);
558 * get the sequence's groups for positional features
559 * and keep track of which groups are visible
561 Set<String> groups = seq.getFeatures().getFeatureGroups(true);
562 Set<String> visibleGroups = new HashSet<>();
563 for (String group : groups)
565 if (group == null || checkGroupState(group))
567 visibleGroups.add(group);
570 foundGroups.addAll(groups);
573 * get distinct feature types for visible groups
574 * record distinct visible types, and their count and total length
576 Set<String> types = seq.getFeatures().getFeatureTypesForGroups(true,
577 visibleGroups.toArray(new String[visibleGroups.size()]));
578 for (String type : types)
580 displayableTypes.add(type);
581 float[] avWidth = typeWidth.get(type);
584 avWidth = new float[2];
585 typeWidth.put(type, avWidth);
587 // todo this could include features with a non-visible group
588 // - do we greatly care?
589 // todo should we include non-displayable features here, and only
590 // update when features are added?
591 avWidth[0] += seq.getFeatures().getFeatureCount(true, type);
592 avWidth[1] += seq.getFeatures().getTotalFeatureLength(type);
596 Object[][] data = new Object[displayableTypes.size()][COLUMN_COUNT];
599 if (fr.hasRenderOrder())
603 fr.findAllFeatures(groupChanged != null); // prod to update
604 // colourschemes. but don't
606 // First add the checks in the previous render order,
607 // in case the window has been closed and reopened
609 List<String> frl = fr.getRenderOrder();
610 for (int ro = frl.size() - 1; ro > -1; ro--)
612 String type = frl.get(ro);
614 if (!displayableTypes.contains(type))
619 data[dataIndex][TYPE_COLUMN] = type;
620 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
621 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
622 data[dataIndex][FILTER_COLUMN] = featureFilter == null
623 ? new FeatureMatcherSet()
625 data[dataIndex][SHOW_COLUMN] = new Boolean(
626 af.getViewport().getFeaturesDisplayed().isVisible(type));
628 displayableTypes.remove(type);
633 * process any extra features belonging only to
634 * a group which was just selected
636 while (!displayableTypes.isEmpty())
638 String type = displayableTypes.iterator().next();
639 data[dataIndex][TYPE_COLUMN] = type;
641 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
642 if (data[dataIndex][COLOUR_COLUMN] == null)
644 // "Colour has been updated in another view!!"
645 fr.clearRenderOrder();
648 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
649 data[dataIndex][FILTER_COLUMN] = featureFilter == null
650 ? new FeatureMatcherSet()
652 data[dataIndex][SHOW_COLUMN] = new Boolean(true);
654 displayableTypes.remove(type);
657 if (originalData == null)
659 originalData = new Object[data.length][COLUMN_COUNT];
660 for (int i = 0; i < data.length; i++)
662 System.arraycopy(data[i], 0, originalData[i], 0, COLUMN_COUNT);
667 updateOriginalData(data);
670 table.setModel(new FeatureTableModel(data));
671 table.getColumnModel().getColumn(0).setPreferredWidth(200);
673 groupPanel.setLayout(
674 new GridLayout(fr.getFeatureGroupsSize() / 4 + 1, 4));
675 pruneGroups(foundGroups);
676 groupPanel.validate();
678 updateFeatureRenderer(data, groupChanged != null);
679 resettingTable = false;
683 * Updates 'originalData' (used for restore on Cancel) if we detect that changes
684 * have been made outwith this dialog
686 * <li>a new feature type added (and made visible)</li>
687 * <li>a feature colour changed (in the Amend Features dialog)</li>
692 protected void updateOriginalData(Object[][] foundData)
694 // todo LinkedHashMap instead of Object[][] would be nice
696 Object[][] currentData = ((FeatureTableModel) table.getModel())
698 for (Object[] row : foundData)
700 String type = (String) row[TYPE_COLUMN];
701 boolean found = false;
702 for (Object[] current : currentData)
704 if (type.equals(current[TYPE_COLUMN]))
708 * currently dependent on object equality here;
709 * really need an equals method on FeatureColour
711 if (!row[COLOUR_COLUMN].equals(current[COLOUR_COLUMN]))
714 * feature colour has changed externally - update originalData
716 for (Object[] original : originalData)
718 if (type.equals(original[TYPE_COLUMN]))
720 original[COLOUR_COLUMN] = row[COLOUR_COLUMN];
731 * new feature detected - add to original data (on top)
733 Object[][] newData = new Object[originalData.length
735 for (int i = 0; i < originalData.length; i++)
737 System.arraycopy(originalData[i], 0, newData[i + 1], 0,
741 originalData = newData;
747 * Remove from the groups panel any checkboxes for groups that are not in the
748 * foundGroups set. This enables removing a group from the display when the last
749 * feature in that group is deleted.
753 protected void pruneGroups(Set<String> foundGroups)
755 for (int g = 0; g < groupPanel.getComponentCount(); g++)
757 JCheckBox checkbox = (JCheckBox) groupPanel.getComponent(g);
758 if (!foundGroups.contains(checkbox.getText()))
760 groupPanel.remove(checkbox);
766 * reorder data based on the featureRenderers global priority list.
770 private void ensureOrder(Object[][] data)
772 boolean sort = false;
773 float[] order = new float[data.length];
774 for (int i = 0; i < order.length; i++)
776 order[i] = fr.getOrder(data[i][0].toString());
779 order[i] = fr.setOrder(data[i][0].toString(), i / order.length);
783 sort = sort || order[i - 1] > order[i];
788 jalview.util.QuickSort.sort(order, data);
793 * Offers a file chooser dialog, and then loads the feature colours and
794 * filters from file in XML format and unmarshals to Jalview feature settings
798 JalviewFileChooser chooser = new JalviewFileChooser("fc",
799 SEQUENCE_FEATURE_COLOURS);
800 chooser.setFileView(new JalviewFileView());
801 chooser.setDialogTitle(
802 MessageManager.getString("label.load_feature_colours"));
803 chooser.setToolTipText(MessageManager.getString("action.load"));
805 int value = chooser.showOpenDialog(this);
807 if (value == JalviewFileChooser.APPROVE_OPTION)
809 File file = chooser.getSelectedFile();
815 * Loads feature colours and filters from XML stored in the given file
823 InputStreamReader in = new InputStreamReader(
824 new FileInputStream(file), "UTF-8");
826 JAXBContext jc = JAXBContext
827 .newInstance("jalview.xml.binding.jalview");
828 javax.xml.bind.Unmarshaller um = jc.createUnmarshaller();
829 XMLStreamReader streamReader = XMLInputFactory.newInstance()
830 .createXMLStreamReader(in);
831 JAXBElement<JalviewUserColours> jbe = um.unmarshal(streamReader,
832 JalviewUserColours.class);
833 JalviewUserColours jucs = jbe.getValue();
835 // JalviewUserColours jucs = JalviewUserColours.unmarshal(in);
838 * load feature colours
840 for (int i = jucs.getColour().size() - 1; i >= 0; i--)
842 Colour newcol = jucs.getColour().get(i);
843 FeatureColourI colour = jalview.project.Jalview2XML
844 .parseColour(newcol);
845 fr.setColour(newcol.getName(), colour);
846 fr.setOrder(newcol.getName(), i / (float) jucs.getColour().size());
850 * load feature filters; loaded filters will replace any that are
851 * currently defined, other defined filters are left unchanged
853 for (int i = 0; i < jucs.getFilter().size(); i++)
855 Filter filterModel = jucs.getFilter().get(i);
856 String featureType = filterModel.getFeatureType();
857 FeatureMatcherSetI filter = jalview.project.Jalview2XML
858 .parseFilter(featureType, filterModel.getMatcherSet());
859 if (!filter.isEmpty())
861 fr.setFeatureFilter(featureType, filter);
866 * update feature settings table
871 Object[][] data = ((FeatureTableModel) table.getModel())
874 updateFeatureRenderer(data, false);
877 } catch (Exception ex)
879 System.out.println("Error loading User Colour File\n" + ex);
884 * Offers a file chooser dialog, and then saves the current feature colours
885 * and any filters to the selected file in XML format
889 JalviewFileChooser chooser = new JalviewFileChooser("fc",
890 SEQUENCE_FEATURE_COLOURS);
891 chooser.setFileView(new JalviewFileView());
892 chooser.setDialogTitle(
893 MessageManager.getString("label.save_feature_colours"));
894 chooser.setToolTipText(MessageManager.getString("action.save"));
896 int value = chooser.showSaveDialog(this);
898 if (value == JalviewFileChooser.APPROVE_OPTION)
900 save(chooser.getSelectedFile());
905 * Saves feature colours and filters to the given file
911 JalviewUserColours ucs = new JalviewUserColours();
912 ucs.setSchemeName("Sequence Features");
915 PrintWriter out = new PrintWriter(new OutputStreamWriter(
916 new FileOutputStream(file), "UTF-8"));
919 * sort feature types by colour order, from 0 (highest)
922 Set<String> fr_colours = fr.getAllFeatureColours();
923 String[] sortedTypes = fr_colours
924 .toArray(new String[fr_colours.size()]);
925 Arrays.sort(sortedTypes, new Comparator<String>()
928 public int compare(String type1, String type2)
930 return Float.compare(fr.getOrder(type1), fr.getOrder(type2));
935 * save feature colours
937 for (String featureType : sortedTypes)
939 FeatureColourI fcol = fr.getFeatureStyle(featureType);
940 Colour col = jalview.project.Jalview2XML.marshalColour(featureType,
942 ucs.getColour().add(col);
946 * save any feature filters
948 for (String featureType : sortedTypes)
950 FeatureMatcherSetI filter = fr.getFeatureFilter(featureType);
951 if (filter != null && !filter.isEmpty())
953 Iterator<FeatureMatcherI> iterator = filter.getMatchers().iterator();
954 FeatureMatcherI firstMatcher = iterator.next();
955 jalview.xml.binding.jalview.FeatureMatcherSet ms = jalview.project.Jalview2XML
956 .marshalFilter(firstMatcher, iterator,
958 Filter filterModel = new Filter();
959 filterModel.setFeatureType(featureType);
960 filterModel.setMatcherSet(ms);
961 ucs.getFilter().add(filterModel);
964 JAXBContext jaxbContext = JAXBContext
965 .newInstance(JalviewUserColours.class);
966 Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
967 jaxbMarshaller.marshal(
968 new ObjectFactory().createJalviewUserColours(ucs), out);
970 // jaxbMarshaller.marshal(object, pout);
971 // marshaller.marshal(object);
976 } catch (Exception ex)
978 ex.printStackTrace();
982 public void invertSelection()
984 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
985 for (int i = 0; i < data.length; i++)
987 data[i][SHOW_COLUMN] = !(Boolean) data[i][SHOW_COLUMN];
989 updateFeatureRenderer(data, true);
993 public void orderByAvWidth()
995 if (table == null || table.getModel() == null)
999 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1000 float[] width = new float[data.length];
1004 for (int i = 0; i < data.length; i++)
1006 awidth = typeWidth.get(data[i][TYPE_COLUMN]);
1009 width[i] = awidth[1] / awidth[0];// *awidth[0]*awidth[2]; - better
1010 // weight - but have to make per
1011 // sequence, too (awidth[2])
1012 // if (width[i]==1) // hack to distinguish single width sequences.
1023 boolean sort = false;
1024 for (int i = 0; i < width.length; i++)
1026 // awidth = (float[]) typeWidth.get(data[i][0]);
1029 width[i] = fr.getOrder(data[i][TYPE_COLUMN].toString());
1032 width[i] = fr.setOrder(data[i][TYPE_COLUMN].toString(),
1038 width[i] /= max; // normalize
1039 fr.setOrder(data[i][TYPE_COLUMN].toString(), width[i]); // store for later
1043 sort = sort || width[i - 1] > width[i];
1048 jalview.util.QuickSort.sort(width, data);
1049 // update global priority order
1052 updateFeatureRenderer(data, false);
1060 frame.setClosed(true);
1061 } catch (Exception exe)
1067 public void updateFeatureRenderer(Object[][] data)
1069 updateFeatureRenderer(data, true);
1073 * Update the priority order of features; only repaint if this changed the order
1074 * of visible features
1079 private void updateFeatureRenderer(Object[][] data, boolean visibleNew)
1081 FeatureSettingsBean[] rowData = getTableAsBeans(data);
1083 if (fr.setFeaturePriority(rowData, visibleNew))
1090 * Converts table data into an array of data beans
1092 private FeatureSettingsBean[] getTableAsBeans(Object[][] data)
1094 FeatureSettingsBean[] rowData = new FeatureSettingsBean[data.length];
1095 for (int i = 0; i < data.length; i++)
1097 String type = (String) data[i][TYPE_COLUMN];
1098 FeatureColourI colour = (FeatureColourI) data[i][COLOUR_COLUMN];
1099 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) data[i][FILTER_COLUMN];
1100 Boolean isShown = (Boolean) data[i][SHOW_COLUMN];
1101 rowData[i] = new FeatureSettingsBean(type, colour, theFilter,
1107 private void jbInit() throws Exception
1109 this.setLayout(new BorderLayout());
1111 JPanel settingsPane = new JPanel();
1112 settingsPane.setLayout(new BorderLayout());
1114 JPanel bigPanel = new JPanel();
1115 bigPanel.setLayout(new BorderLayout());
1117 groupPanel = new JPanel();
1118 bigPanel.add(groupPanel, BorderLayout.NORTH);
1120 JButton invert = new JButton(
1121 MessageManager.getString("label.invert_selection"));
1122 invert.setFont(JvSwingUtils.getLabelFont());
1123 invert.addActionListener(new ActionListener()
1126 public void actionPerformed(ActionEvent e)
1132 JButton optimizeOrder = new JButton(
1133 MessageManager.getString("label.optimise_order"));
1134 optimizeOrder.setFont(JvSwingUtils.getLabelFont());
1135 optimizeOrder.addActionListener(new ActionListener()
1138 public void actionPerformed(ActionEvent e)
1144 JButton sortByScore = new JButton(
1145 MessageManager.getString("label.seq_sort_by_score"));
1146 sortByScore.setFont(JvSwingUtils.getLabelFont());
1147 sortByScore.addActionListener(new ActionListener()
1150 public void actionPerformed(ActionEvent e)
1152 af.avc.sortAlignmentByFeatureScore(null);
1155 JButton sortByDens = new JButton(
1156 MessageManager.getString("label.sequence_sort_by_density"));
1157 sortByDens.setFont(JvSwingUtils.getLabelFont());
1158 sortByDens.addActionListener(new ActionListener()
1161 public void actionPerformed(ActionEvent e)
1163 af.avc.sortAlignmentByFeatureDensity(null);
1167 JButton help = new JButton(MessageManager.getString("action.help"));
1168 help.setFont(JvSwingUtils.getLabelFont());
1169 help.addActionListener(new ActionListener()
1172 public void actionPerformed(ActionEvent e)
1176 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1177 } catch (HelpSetException e1)
1179 e1.printStackTrace();
1183 help.setFont(JvSwingUtils.getLabelFont());
1184 help.setText(MessageManager.getString("action.help"));
1185 help.addActionListener(new ActionListener()
1188 public void actionPerformed(ActionEvent e)
1192 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1193 } catch (HelpSetException e1)
1195 e1.printStackTrace();
1200 JButton cancel = new JButton(MessageManager.getString("action.cancel"));
1201 cancel.setFont(JvSwingUtils.getLabelFont());
1202 cancel.addActionListener(new ActionListener()
1205 public void actionPerformed(ActionEvent e)
1207 fr.setTransparency(originalTransparency);
1208 fr.setFeatureFilters(originalFilters);
1209 updateFeatureRenderer(originalData);
1214 JButton ok = new JButton(MessageManager.getString("action.ok"));
1215 ok.setFont(JvSwingUtils.getLabelFont());
1216 ok.addActionListener(new ActionListener()
1219 public void actionPerformed(ActionEvent e)
1225 JButton loadColours = new JButton(
1226 MessageManager.getString("label.load_colours"));
1227 loadColours.setFont(JvSwingUtils.getLabelFont());
1228 loadColours.setToolTipText(
1229 MessageManager.getString("label.load_colours_tooltip"));
1230 loadColours.addActionListener(new ActionListener()
1233 public void actionPerformed(ActionEvent e)
1239 JButton saveColours = new JButton(
1240 MessageManager.getString("label.save_colours"));
1241 saveColours.setFont(JvSwingUtils.getLabelFont());
1242 saveColours.setToolTipText(
1243 MessageManager.getString("label.save_colours_tooltip"));
1244 saveColours.addActionListener(new ActionListener()
1247 public void actionPerformed(ActionEvent e)
1252 transparency.addChangeListener(new ChangeListener()
1255 public void stateChanged(ChangeEvent evt)
1257 if (!inConstruction)
1259 fr.setTransparency((100 - transparency.getValue()) / 100f);
1265 transparency.setMaximum(70);
1266 transparency.setToolTipText(
1267 MessageManager.getString("label.transparency_tip"));
1269 boolean nucleotide = af.getViewport().getAlignment().isNucleotide();
1270 showComplement = new JCheckBox(
1271 "Show " + (nucleotide ? "protein" : "CDS") + " features");
1272 showComplement.setSelected(af.getViewport().isShowComplementFeatures());
1273 showComplement.addActionListener(new ActionListener()
1276 public void actionPerformed(ActionEvent e)
1279 .setShowComplementFeatures(showComplement.isSelected());
1284 showComplementOnTop = new JCheckBox("on top");
1286 .setSelected(af.getViewport().isShowComplementFeaturesOnTop());
1287 showComplementOnTop.addActionListener(new ActionListener()
1290 public void actionPerformed(ActionEvent e)
1292 af.getViewport().setShowComplementFeaturesOnTop(
1293 showComplementOnTop.isSelected());
1298 JPanel lowerPanel = new JPanel(new GridLayout(1, 2));
1299 bigPanel.add(lowerPanel, BorderLayout.SOUTH);
1301 JPanel transbuttons = new JPanel(new GridLayout(5, 1));
1302 transbuttons.add(optimizeOrder);
1303 transbuttons.add(invert);
1304 transbuttons.add(sortByScore);
1305 transbuttons.add(sortByDens);
1306 transbuttons.add(help);
1308 boolean hasComplement = af.getViewport().getCodingComplement() != null;
1309 JPanel transPanelLeft = new JPanel(
1310 new GridLayout(hasComplement ? 3 : 2, 1));
1311 transPanelLeft.add(new JLabel(" Colour transparency" + ":"));
1312 transPanelLeft.add(transparency);
1315 JPanel cp = new JPanel(new FlowLayout(FlowLayout.LEFT));
1316 cp.add(showComplement);
1317 cp.add(showComplementOnTop);
1318 transPanelLeft.add(cp);
1320 lowerPanel.add(transPanelLeft);
1321 lowerPanel.add(transbuttons);
1323 JPanel buttonPanel = new JPanel();
1324 buttonPanel.add(ok);
1325 buttonPanel.add(cancel);
1326 buttonPanel.add(loadColours);
1327 buttonPanel.add(saveColours);
1328 bigPanel.add(scrollPane, BorderLayout.CENTER);
1329 settingsPane.add(bigPanel, BorderLayout.CENTER);
1330 settingsPane.add(buttonPanel, BorderLayout.SOUTH);
1331 this.add(settingsPane);
1335 * Repaints alignment, structure and overview (if shown). If there is a
1336 * complementary view which is showing this view's features, then also
1339 void refreshDisplay()
1341 af.alignPanel.paintAlignment(true, true);
1342 AlignViewportI complement = af.getViewport().getCodingComplement();
1343 if (complement != null && complement.isShowComplementFeatures())
1345 AlignFrame af2 = Desktop.getAlignFrameFor(complement);
1346 af2.alignPanel.paintAlignment(true, true);
1350 // ///////////////////////////////////////////////////////////////////////
1351 // http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
1352 // ///////////////////////////////////////////////////////////////////////
1353 class FeatureTableModel extends AbstractTableModel
1355 private String[] columnNames = {
1356 MessageManager.getString("label.feature_type"),
1357 MessageManager.getString("action.colour"),
1358 MessageManager.getString("label.configuration"),
1359 MessageManager.getString("label.show") };
1361 private Object[][] data;
1363 FeatureTableModel(Object[][] data)
1368 public Object[][] getData()
1373 public void setData(Object[][] data)
1379 public int getColumnCount()
1381 return columnNames.length;
1384 public Object[] getRow(int row)
1390 public int getRowCount()
1396 public String getColumnName(int col)
1398 return columnNames[col];
1402 public Object getValueAt(int row, int col)
1404 return data[row][col];
1408 * Answers the class of column c of the table
1411 public Class<?> getColumnClass(int c)
1416 return String.class;
1418 return FeatureColour.class;
1420 return FeatureMatcherSet.class;
1422 return Boolean.class;
1427 public boolean isCellEditable(int row, int col)
1429 return col == 0 ? false : true;
1433 public void setValueAt(Object value, int row, int col)
1435 data[row][col] = value;
1436 fireTableCellUpdated(row, col);
1437 updateFeatureRenderer(data);
1442 class ColorRenderer extends JLabel implements TableCellRenderer
1444 javax.swing.border.Border unselectedBorder = null;
1446 javax.swing.border.Border selectedBorder = null;
1448 final String baseTT = "Click to edit, right/apple click for menu.";
1450 public ColorRenderer()
1452 setOpaque(true); // MUST do this for background to show up.
1453 setHorizontalTextPosition(SwingConstants.CENTER);
1454 setVerticalTextPosition(SwingConstants.CENTER);
1458 public Component getTableCellRendererComponent(JTable tbl, Object color,
1459 boolean isSelected, boolean hasFocus, int row, int column)
1461 FeatureColourI cellColour = (FeatureColourI) color;
1463 setToolTipText(baseTT);
1464 setBackground(tbl.getBackground());
1465 if (!cellColour.isSimpleColour())
1467 Rectangle cr = tbl.getCellRect(row, column, false);
1468 FeatureSettings.renderGraduatedColor(this, cellColour,
1469 (int) cr.getWidth(), (int) cr.getHeight());
1475 setBackground(cellColour.getColour());
1479 if (selectedBorder == null)
1481 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1482 tbl.getSelectionBackground());
1484 setBorder(selectedBorder);
1488 if (unselectedBorder == null)
1490 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1491 tbl.getBackground());
1493 setBorder(unselectedBorder);
1500 class FilterRenderer extends JLabel implements TableCellRenderer
1502 javax.swing.border.Border unselectedBorder = null;
1504 javax.swing.border.Border selectedBorder = null;
1506 public FilterRenderer()
1508 setOpaque(true); // MUST do this for background to show up.
1509 setHorizontalTextPosition(SwingConstants.CENTER);
1510 setVerticalTextPosition(SwingConstants.CENTER);
1514 public Component getTableCellRendererComponent(JTable tbl,
1515 Object filter, boolean isSelected, boolean hasFocus, int row,
1518 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) filter;
1520 String asText = theFilter.toString();
1521 setBackground(tbl.getBackground());
1522 this.setText(asText);
1527 if (selectedBorder == null)
1529 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1530 tbl.getSelectionBackground());
1532 setBorder(selectedBorder);
1536 if (unselectedBorder == null)
1538 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1539 tbl.getBackground());
1541 setBorder(unselectedBorder);
1549 * update comp using rendering settings from gcol
1554 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol)
1556 int w = comp.getWidth(), h = comp.getHeight();
1559 w = (int) comp.getPreferredSize().getWidth();
1560 h = (int) comp.getPreferredSize().getHeight();
1567 renderGraduatedColor(comp, gcol, w, h);
1570 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol,
1573 boolean thr = false;
1574 StringBuilder tt = new StringBuilder();
1575 StringBuilder tx = new StringBuilder();
1577 if (gcol.isColourByAttribute())
1579 tx.append(String.join(":", gcol.getAttributeName()));
1581 else if (!gcol.isColourByLabel())
1583 tx.append(MessageManager.getString("label.score"));
1586 if (gcol.isAboveThreshold())
1590 tt.append("Thresholded (Above ").append(gcol.getThreshold())
1593 if (gcol.isBelowThreshold())
1597 tt.append("Thresholded (Below ").append(gcol.getThreshold())
1600 if (gcol.isColourByLabel())
1602 tt.append("Coloured by label text. ").append(tt);
1607 if (!gcol.isColourByAttribute())
1615 Color newColor = gcol.getMaxColour();
1616 comp.setBackground(newColor);
1617 // System.err.println("Width is " + w / 2);
1618 Icon ficon = new FeatureIcon(gcol, comp.getBackground(), w, h, thr);
1619 comp.setIcon(ficon);
1620 // tt+="RGB value: Max (" + newColor.getRed() + ", "
1621 // + newColor.getGreen() + ", " + newColor.getBlue()
1622 // + ")\nMin (" + minCol.getRed() + ", " + minCol.getGreen()
1623 // + ", " + minCol.getBlue() + ")");
1625 comp.setHorizontalAlignment(SwingConstants.CENTER);
1626 comp.setText(tx.toString());
1627 if (tt.length() > 0)
1629 if (comp.getToolTipText() == null)
1631 comp.setToolTipText(tt.toString());
1635 comp.setToolTipText(
1636 tt.append(" ").append(comp.getToolTipText()).toString());
1641 class ColorEditor extends AbstractCellEditor
1642 implements TableCellEditor, ActionListener
1646 FeatureColourI currentColor;
1648 FeatureTypeSettings chooser;
1654 JColorChooser colorChooser;
1658 protected static final String EDIT = "edit";
1660 int rowSelected = 0;
1662 public ColorEditor(FeatureSettings me)
1665 // Set up the editor (from the table's point of view),
1666 // which is a button.
1667 // This button brings up the color chooser dialog,
1668 // which is the editor from the user's point of view.
1669 button = new JButton();
1670 button.setActionCommand(EDIT);
1671 button.addActionListener(this);
1672 button.setBorderPainted(false);
1673 // Set up the dialog that the button brings up.
1674 colorChooser = new JColorChooser();
1675 dialog = JColorChooser.createDialog(button,
1676 MessageManager.getString("label.select_colour"), true, // modal
1677 colorChooser, this, // OK button handler
1678 null); // no CANCEL button handler
1682 * Handles events from the editor button and from the dialog's OK button.
1685 public void actionPerformed(ActionEvent e)
1687 // todo test e.getSource() instead here
1688 if (EDIT.equals(e.getActionCommand()))
1690 // The user has clicked the cell, so
1691 // bring up the dialog.
1692 if (currentColor.isSimpleColour())
1694 // bring up simple color chooser
1695 button.setBackground(currentColor.getColour());
1696 colorChooser.setColor(currentColor.getColour());
1697 dialog.setVisible(true);
1701 // bring up graduated chooser.
1702 chooser = new FeatureTypeSettings(me.fr, type);
1707 chooser.setRequestFocusEnabled(true);
1708 chooser.requestFocus();
1710 chooser.addActionListener(this);
1711 // Make the renderer reappear.
1712 fireEditingStopped();
1717 if (currentColor.isSimpleColour())
1720 * read off colour picked in colour chooser after OK pressed
1722 currentColor = new FeatureColour(colorChooser.getColor());
1723 me.table.setValueAt(currentColor, rowSelected, COLOUR_COLUMN);
1728 * after OK in variable colour dialog, any changes to colour
1729 * (or filters!) are already set in FeatureRenderer, so just
1730 * update table data without triggering updateFeatureRenderer
1732 currentColor = fr.getFeatureColours().get(type);
1733 FeatureMatcherSetI currentFilter = me.fr.getFeatureFilter(type);
1734 if (currentFilter == null)
1736 currentFilter = new FeatureMatcherSet();
1738 Object[] data = ((FeatureTableModel) table.getModel())
1739 .getData()[rowSelected];
1740 data[COLOUR_COLUMN] = currentColor;
1741 data[FILTER_COLUMN] = currentFilter;
1743 fireEditingStopped();
1744 me.table.validate();
1748 // Implement the one CellEditor method that AbstractCellEditor doesn't.
1750 public Object getCellEditorValue()
1752 return currentColor;
1755 // Implement the one method defined by TableCellEditor.
1757 public Component getTableCellEditorComponent(JTable theTable, Object value,
1758 boolean isSelected, int row, int column)
1760 currentColor = (FeatureColourI) value;
1761 this.rowSelected = row;
1762 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1763 button.setOpaque(true);
1764 button.setBackground(me.getBackground());
1765 if (!currentColor.isSimpleColour())
1767 JLabel btn = new JLabel();
1768 btn.setSize(button.getSize());
1769 FeatureSettings.renderGraduatedColor(btn, currentColor);
1770 button.setBackground(btn.getBackground());
1771 button.setIcon(btn.getIcon());
1772 button.setText(btn.getText());
1777 button.setIcon(null);
1778 button.setBackground(currentColor.getColour());
1785 * The cell editor for the Filter column. It displays the text of any filters
1786 * for the feature type in that row (in full as a tooltip, possible abbreviated
1787 * as display text). On click in the cell, opens the Feature Display Settings
1788 * dialog at the Filters tab.
1790 class FilterEditor extends AbstractCellEditor
1791 implements TableCellEditor, ActionListener
1795 FeatureMatcherSetI currentFilter;
1803 protected static final String EDIT = "edit";
1805 int rowSelected = 0;
1807 public FilterEditor(FeatureSettings me)
1810 button = new JButton();
1811 button.setActionCommand(EDIT);
1812 button.addActionListener(this);
1813 button.setBorderPainted(false);
1817 * Handles events from the editor button
1820 public void actionPerformed(ActionEvent e)
1822 if (button == e.getSource())
1824 FeatureTypeSettings chooser = new FeatureTypeSettings(me.fr, type);
1825 chooser.addActionListener(this);
1826 chooser.setRequestFocusEnabled(true);
1827 chooser.requestFocus();
1828 if (lastLocation != null)
1830 // todo open at its last position on screen
1831 chooser.setBounds(lastLocation.x, lastLocation.y,
1832 chooser.getWidth(), chooser.getHeight());
1835 fireEditingStopped();
1837 else if (e.getSource() instanceof Component)
1841 * after OK in variable colour dialog, any changes to filter
1842 * (or colours!) are already set in FeatureRenderer, so just
1843 * update table data without triggering updateFeatureRenderer
1845 FeatureColourI currentColor = fr.getFeatureColours().get(type);
1846 currentFilter = me.fr.getFeatureFilter(type);
1847 if (currentFilter == null)
1849 currentFilter = new FeatureMatcherSet();
1851 Object[] data = ((FeatureTableModel) table.getModel())
1852 .getData()[rowSelected];
1853 data[COLOUR_COLUMN] = currentColor;
1854 data[FILTER_COLUMN] = currentFilter;
1855 fireEditingStopped();
1856 me.table.validate();
1861 public Object getCellEditorValue()
1863 return currentFilter;
1867 public Component getTableCellEditorComponent(JTable theTable, Object value,
1868 boolean isSelected, int row, int column)
1870 currentFilter = (FeatureMatcherSetI) value;
1871 this.rowSelected = row;
1872 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1873 button.setOpaque(true);
1874 button.setBackground(me.getBackground());
1875 button.setText(currentFilter.toString());
1876 button.setToolTipText(currentFilter.toString());
1877 button.setIcon(null);
1883 class FeatureIcon implements Icon
1885 FeatureColourI gcol;
1889 boolean midspace = false;
1891 int width = 50, height = 20;
1893 int s1, e1; // start and end of midpoint band for thresholded symbol
1895 Color mpcolour = Color.white;
1897 FeatureIcon(FeatureColourI gfc, Color bg, int w, int h, boolean mspace)
1917 public int getIconWidth()
1923 public int getIconHeight()
1929 public void paintIcon(Component c, Graphics g, int x, int y)
1932 if (gcol.isColourByLabel())
1935 g.fillRect(0, 0, width, height);
1936 // need an icon here.
1937 g.setColor(gcol.getMaxColour());
1939 g.setFont(new Font("Verdana", Font.PLAIN, 9));
1941 // g.setFont(g.getFont().deriveFont(
1942 // AffineTransform.getScaleInstance(
1943 // width/g.getFontMetrics().stringWidth("Label"),
1944 // height/g.getFontMetrics().getHeight())));
1946 g.drawString(MessageManager.getString("label.label"), 0, 0);
1951 Color minCol = gcol.getMinColour();
1953 g.fillRect(0, 0, s1, height);
1956 g.setColor(Color.white);
1957 g.fillRect(s1, 0, e1 - s1, height);
1959 g.setColor(gcol.getMaxColour());
1960 g.fillRect(0, e1, width - e1, height);