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.util.dialogrunner.RunResponse;
39 import jalview.viewmodel.seqfeatures.FeatureRendererModel.FeatureSettingsBean;
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;
50 import java.awt.Graphics;
51 import java.awt.GridLayout;
52 import java.awt.Point;
53 import java.awt.Rectangle;
54 import java.awt.event.ActionEvent;
55 import java.awt.event.ActionListener;
56 import java.awt.event.ItemEvent;
57 import java.awt.event.ItemListener;
58 import java.awt.event.MouseAdapter;
59 import java.awt.event.MouseEvent;
60 import java.awt.event.MouseMotionAdapter;
61 import java.beans.PropertyChangeEvent;
62 import java.beans.PropertyChangeListener;
64 import java.io.FileInputStream;
65 import java.io.FileOutputStream;
66 import java.io.InputStreamReader;
67 import java.io.OutputStreamWriter;
68 import java.io.PrintWriter;
69 import java.util.Arrays;
70 import java.util.Comparator;
71 import java.util.HashMap;
72 import java.util.HashSet;
73 import java.util.Hashtable;
74 import java.util.Iterator;
75 import java.util.List;
79 import javax.help.HelpSetException;
80 import javax.swing.AbstractCellEditor;
81 import javax.swing.BorderFactory;
82 import javax.swing.Icon;
83 import javax.swing.JButton;
84 import javax.swing.JCheckBox;
85 import javax.swing.JCheckBoxMenuItem;
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.ToolTipManager;
98 import javax.swing.border.Border;
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 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 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 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());
216 int row = table.rowAtPoint(e.getPoint());
221 tip = JvSwingUtils.wrapTooltip(true, MessageManager
222 .getString("label.feature_settings_click_drag"));
225 FeatureColourI colour = (FeatureColourI) table.getValueAt(row,
227 tip = getColorTooltip(colour, true);
230 FeatureMatcherSet o = (FeatureMatcherSet) table.getValueAt(row,
233 ? MessageManager.getString("label.filters_tooltip")
244 * Position the tooltip near the bottom edge of, and half way across, the
248 public Point getToolTipLocation(MouseEvent e)
250 Point point = e.getPoint();
251 int column = table.columnAtPoint(point);
252 int row = table.rowAtPoint(point);
253 Rectangle r = getCellRect(row, column, false);
254 Point loc = new Point(r.x + r.width / 2, r.y + r.height - 3);
258 table.getTableHeader().setFont(new Font("Verdana", Font.PLAIN, 12));
259 ToolTipManager.sharedInstance().registerComponent(table);
261 table.setDefaultEditor(FeatureColour.class, new ColorEditor(this));
262 table.setDefaultRenderer(FeatureColour.class, new ColorRenderer());
264 table.setDefaultEditor(FeatureMatcherSet.class, new FilterEditor(this));
265 table.setDefaultRenderer(FeatureMatcherSet.class, new FilterRenderer());
267 TableColumn colourColumn = new TableColumn(COLOUR_COLUMN, 75,
268 new ColorRenderer(), new ColorEditor(this));
269 table.addColumn(colourColumn);
271 TableColumn filterColumn = new TableColumn(FILTER_COLUMN, 75,
272 new FilterRenderer(), new FilterEditor(this));
273 table.addColumn(filterColumn);
275 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
277 table.addMouseListener(new MouseAdapter()
280 public void mousePressed(MouseEvent evt)
282 selectedRow = table.rowAtPoint(evt.getPoint());
283 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
284 if (evt.isPopupTrigger())
286 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
287 popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
290 else if (evt.getClickCount() == 2)
292 boolean invertSelection = evt.isAltDown();
293 boolean toggleSelection = Platform.isControlDown(evt);
294 boolean extendSelection = evt.isShiftDown();
295 fr.ap.alignFrame.avc.markColumnsContainingFeatures(
296 invertSelection, extendSelection, toggleSelection, type);
300 // isPopupTrigger fires on mouseReleased on Windows
302 public void mouseReleased(MouseEvent evt)
304 selectedRow = table.rowAtPoint(evt.getPoint());
305 if (evt.isPopupTrigger())
307 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
308 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
309 popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
315 table.addMouseMotionListener(new MouseMotionAdapter()
318 public void mouseDragged(MouseEvent evt)
320 int newRow = table.rowAtPoint(evt.getPoint());
321 if (newRow != selectedRow && selectedRow != -1 && newRow != -1)
324 * reposition 'selectedRow' to 'newRow' (the dragged to location)
325 * this could be more than one row away for a very fast drag action
326 * so just swap it with adjacent rows until we get it there
328 Object[][] data = ((FeatureTableModel) table.getModel())
330 int direction = newRow < selectedRow ? -1 : 1;
331 for (int i = selectedRow; i != newRow; i += direction)
333 Object[] temp = data[i];
334 data[i] = data[i + direction];
335 data[i + direction] = temp;
337 updateFeatureRenderer(data);
339 selectedRow = newRow;
343 // table.setToolTipText(JvSwingUtils.wrapTooltip(true,
344 // MessageManager.getString("label.feature_settings_click_drag")));
345 scrollPane.setViewportView(table);
347 if (af.getViewport().isShowSequenceFeatures() || !fr.hasRenderOrder())
349 fr.findAllFeatures(true); // display everything!
352 discoverAllFeatureData();
353 final PropertyChangeListener change;
354 final FeatureSettings fs = this;
355 fr.addPropertyChangeListener(change = new PropertyChangeListener()
358 public void propertyChange(PropertyChangeEvent evt)
360 if (!fs.resettingTable && !fs.handlingUpdate)
362 fs.handlingUpdate = true;
364 // new groups may be added with new sequence feature types only
365 fs.handlingUpdate = false;
371 frame = new JInternalFrame();
372 frame.setContentPane(this);
373 if (Platform.isAMac())
375 Desktop.addInternalFrame(frame,
376 MessageManager.getString("label.sequence_feature_settings"),
381 Desktop.addInternalFrame(frame,
382 MessageManager.getString("label.sequence_feature_settings"),
385 frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
387 frame.addInternalFrameListener(
388 new javax.swing.event.InternalFrameAdapter()
391 public void internalFrameClosed(
392 javax.swing.event.InternalFrameEvent evt)
394 fr.removePropertyChangeListener(change);
397 frame.setLayer(JLayeredPane.PALETTE_LAYER);
398 inConstruction = false;
401 protected void popupSort(final int rowSelected, final String type,
402 final Object typeCol, final Map<String, float[][]> minmax, int x,
405 final FeatureColourI featureColour = (FeatureColourI) typeCol;
407 JPopupMenu men = new JPopupMenu(MessageManager
408 .formatMessage("label.settings_for_param", new String[]
410 JMenuItem scr = new JMenuItem(
411 MessageManager.getString("label.sort_by_score"));
413 final FeatureSettings me = this;
414 scr.addActionListener(new ActionListener()
418 public void actionPerformed(ActionEvent e)
421 .sortAlignmentByFeatureScore(Arrays.asList(new String[]
426 JMenuItem dens = new JMenuItem(
427 MessageManager.getString("label.sort_by_density"));
428 dens.addActionListener(new ActionListener()
432 public void actionPerformed(ActionEvent e)
435 .sortAlignmentByFeatureDensity(Arrays.asList(new String[]
443 * variable colour options include colour by label, by score,
444 * by selected attribute text, or attribute value
446 final JCheckBoxMenuItem variableColourCB = new JCheckBoxMenuItem(
447 MessageManager.getString("label.variable_colour"));
448 variableColourCB.setSelected(!featureColour.isSimpleColour());
449 men.add(variableColourCB);
452 * checkbox action listener doubles up as listener to OK
453 * from the variable colour / filters dialog
455 variableColourCB.addActionListener(new ActionListener()
458 public void actionPerformed(ActionEvent e)
460 if (e.getSource() == variableColourCB)
462 if (featureColour.isSimpleColour())
465 * toggle simple colour to variable colour - show dialog
467 FeatureTypeSettings fc = new FeatureTypeSettings(me.fr, type);
468 fc.addActionListener(this);
473 * toggle variable to simple colour - show colour chooser
475 String title = MessageManager.getString("label.select_colour");
476 ColourChooserListener listener = new ColourChooserListener()
479 public void colourSelected(Color c)
481 table.setValueAt(new FeatureColour(c), rowSelected,
484 me.updateFeatureRenderer(
485 ((FeatureTableModel) table.getModel()).getData(),
489 JalviewColourChooser.showColourChooser(me, title, featureColour.getMaxColour(), listener);
493 if (e.getSource() instanceof FeatureTypeSettings)
496 * update after OK in feature colour dialog; the updated
497 * colour will have already been set in the FeatureRenderer
499 FeatureColourI fci = fr.getFeatureColours().get(type);
500 table.setValueAt(fci, rowSelected, COLOUR_COLUMN);
508 JMenuItem selCols = new JMenuItem(
509 MessageManager.getString("label.select_columns_containing"));
510 selCols.addActionListener(new ActionListener()
513 public void actionPerformed(ActionEvent arg0)
515 fr.ap.alignFrame.avc.markColumnsContainingFeatures(false, false,
519 JMenuItem clearCols = new JMenuItem(MessageManager
520 .getString("label.select_columns_not_containing"));
521 clearCols.addActionListener(new ActionListener()
524 public void actionPerformed(ActionEvent arg0)
526 fr.ap.alignFrame.avc.markColumnsContainingFeatures(true, false,
530 JMenuItem hideCols = new JMenuItem(
531 MessageManager.getString("label.hide_columns_containing"));
532 hideCols.addActionListener(new ActionListener()
535 public void actionPerformed(ActionEvent arg0)
537 fr.ap.alignFrame.hideFeatureColumns(type, true);
540 JMenuItem hideOtherCols = new JMenuItem(
541 MessageManager.getString("label.hide_columns_not_containing"));
542 hideOtherCols.addActionListener(new ActionListener()
545 public void actionPerformed(ActionEvent arg0)
547 fr.ap.alignFrame.hideFeatureColumns(type, false);
553 men.add(hideOtherCols);
554 men.show(table, x, y);
558 synchronized public void discoverAllFeatureData()
560 Set<String> allGroups = new HashSet<>();
561 AlignmentI alignment = af.getViewport().getAlignment();
563 for (int i = 0; i < alignment.getHeight(); i++)
565 SequenceI seq = alignment.getSequenceAt(i);
566 for (String group : seq.getFeatures().getFeatureGroups(true))
568 if (group != null && !allGroups.contains(group))
570 allGroups.add(group);
571 checkGroupState(group);
582 * Synchronise gui group list and check visibility of group
585 * @return true if group is visible
587 private boolean checkGroupState(String group)
589 boolean visible = fr.checkGroupVisibility(group, true);
591 for (int g = 0; g < groupPanel.getComponentCount(); g++)
593 if (((JCheckBox) groupPanel.getComponent(g)).getText().equals(group))
595 ((JCheckBox) groupPanel.getComponent(g)).setSelected(visible);
600 final String grp = group;
601 final JCheckBox check = new JCheckBox(group, visible);
602 check.setFont(new Font("Serif", Font.BOLD, 12));
603 check.setToolTipText(group);
604 check.addItemListener(new ItemListener()
607 public void itemStateChanged(ItemEvent evt)
609 fr.setGroupVisibility(check.getText(), check.isSelected());
610 resetTable(new String[] { grp });
611 af.alignPanel.paintAlignment(true, true);
614 groupPanel.add(check);
618 synchronized void resetTable(String[] groupChanged)
624 resettingTable = true;
625 typeWidth = new Hashtable<>();
626 // TODO: change avWidth calculation to 'per-sequence' average and use long
629 Set<String> displayableTypes = new HashSet<>();
630 Set<String> foundGroups = new HashSet<>();
633 * determine which feature types may be visible depending on
634 * which groups are selected, and recompute average width data
636 for (int i = 0; i < af.getViewport().getAlignment().getHeight(); i++)
639 SequenceI seq = af.getViewport().getAlignment().getSequenceAt(i);
642 * get the sequence's groups for positional features
643 * and keep track of which groups are visible
645 Set<String> groups = seq.getFeatures().getFeatureGroups(true);
646 Set<String> visibleGroups = new HashSet<>();
647 for (String group : groups)
649 if (group == null || checkGroupState(group))
651 visibleGroups.add(group);
654 foundGroups.addAll(groups);
657 * get distinct feature types for visible groups
658 * record distinct visible types, and their count and total length
660 Set<String> types = seq.getFeatures().getFeatureTypesForGroups(true,
661 visibleGroups.toArray(new String[visibleGroups.size()]));
662 for (String type : types)
664 displayableTypes.add(type);
665 float[] avWidth = typeWidth.get(type);
668 avWidth = new float[2];
669 typeWidth.put(type, avWidth);
671 // todo this could include features with a non-visible group
672 // - do we greatly care?
673 // todo should we include non-displayable features here, and only
674 // update when features are added?
675 avWidth[0] += seq.getFeatures().getFeatureCount(true, type);
676 avWidth[1] += seq.getFeatures().getTotalFeatureLength(type);
680 Object[][] data = new Object[displayableTypes.size()][COLUMN_COUNT];
683 if (fr.hasRenderOrder())
687 fr.findAllFeatures(groupChanged != null); // prod to update
688 // colourschemes. but don't
690 // First add the checks in the previous render order,
691 // in case the window has been closed and reopened
693 List<String> frl = fr.getRenderOrder();
694 for (int ro = frl.size() - 1; ro > -1; ro--)
696 String type = frl.get(ro);
698 if (!displayableTypes.contains(type))
703 data[dataIndex][TYPE_COLUMN] = type;
704 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
705 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
706 data[dataIndex][FILTER_COLUMN] = featureFilter == null
707 ? new FeatureMatcherSet()
709 data[dataIndex][SHOW_COLUMN] = new Boolean(
710 af.getViewport().getFeaturesDisplayed().isVisible(type));
712 displayableTypes.remove(type);
717 * process any extra features belonging only to
718 * a group which was just selected
720 while (!displayableTypes.isEmpty())
722 String type = displayableTypes.iterator().next();
723 data[dataIndex][TYPE_COLUMN] = type;
725 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
726 if (data[dataIndex][COLOUR_COLUMN] == null)
728 // "Colour has been updated in another view!!"
729 fr.clearRenderOrder();
732 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
733 data[dataIndex][FILTER_COLUMN] = featureFilter == null
734 ? new FeatureMatcherSet()
736 data[dataIndex][SHOW_COLUMN] = new Boolean(true);
738 displayableTypes.remove(type);
741 if (originalData == null)
743 originalData = new Object[data.length][COLUMN_COUNT];
744 for (int i = 0; i < data.length; i++)
746 System.arraycopy(data[i], 0, originalData[i], 0, COLUMN_COUNT);
751 updateOriginalData(data);
754 table.setModel(new FeatureTableModel(data));
755 table.getColumnModel().getColumn(0).setPreferredWidth(200);
757 groupPanel.setLayout(
758 new GridLayout(fr.getFeatureGroupsSize() / 4 + 1, 4));
759 pruneGroups(foundGroups);
760 groupPanel.validate();
762 updateFeatureRenderer(data, groupChanged != null);
763 resettingTable = false;
767 * Updates 'originalData' (used for restore on Cancel) if we detect that changes
768 * have been made outwith this dialog
770 * <li>a new feature type added (and made visible)</li>
771 * <li>a feature colour changed (in the Amend Features dialog)</li>
776 protected void updateOriginalData(Object[][] foundData)
778 // todo LinkedHashMap instead of Object[][] would be nice
780 Object[][] currentData = ((FeatureTableModel) table.getModel())
782 for (Object[] row : foundData)
784 String type = (String) row[TYPE_COLUMN];
785 boolean found = false;
786 for (Object[] current : currentData)
788 if (type.equals(current[TYPE_COLUMN]))
792 * currently dependent on object equality here;
793 * really need an equals method on FeatureColour
795 if (!row[COLOUR_COLUMN].equals(current[COLOUR_COLUMN]))
798 * feature colour has changed externally - update originalData
800 for (Object[] original : originalData)
802 if (type.equals(original[TYPE_COLUMN]))
804 original[COLOUR_COLUMN] = row[COLOUR_COLUMN];
815 * new feature detected - add to original data (on top)
817 Object[][] newData = new Object[originalData.length
819 for (int i = 0; i < originalData.length; i++)
821 System.arraycopy(originalData[i], 0, newData[i + 1], 0,
825 originalData = newData;
831 * Remove from the groups panel any checkboxes for groups that are not in the
832 * foundGroups set. This enables removing a group from the display when the last
833 * feature in that group is deleted.
837 protected void pruneGroups(Set<String> foundGroups)
839 for (int g = 0; g < groupPanel.getComponentCount(); g++)
841 JCheckBox checkbox = (JCheckBox) groupPanel.getComponent(g);
842 if (!foundGroups.contains(checkbox.getText()))
844 groupPanel.remove(checkbox);
850 * reorder data based on the featureRenderers global priority list.
854 private void ensureOrder(Object[][] data)
856 boolean sort = false;
857 float[] order = new float[data.length];
858 for (int i = 0; i < order.length; i++)
860 order[i] = fr.getOrder(data[i][0].toString());
863 order[i] = fr.setOrder(data[i][0].toString(), i / order.length);
867 sort = sort || order[i - 1] > order[i];
872 jalview.util.QuickSort.sort(order, data);
877 * Offers a file chooser dialog, and then loads the feature colours and
878 * filters from file in XML format and unmarshals to Jalview feature settings
882 JalviewFileChooser chooser = new JalviewFileChooser("fc",
883 SEQUENCE_FEATURE_COLOURS);
884 chooser.setFileView(new JalviewFileView());
885 chooser.setDialogTitle(
886 MessageManager.getString("label.load_feature_colours"));
887 chooser.setToolTipText(MessageManager.getString("action.load"));
888 chooser.response(new RunResponse(JalviewFileChooser.APPROVE_OPTION){
892 File file = chooser.getSelectedFile();
895 chooser.showOpenDialog(this);
899 * Loads feature colours and filters from XML stored in the given file
907 InputStreamReader in = new InputStreamReader(
908 new FileInputStream(file), "UTF-8");
910 JAXBContext jc = JAXBContext
911 .newInstance("jalview.xml.binding.jalview");
912 javax.xml.bind.Unmarshaller um = jc.createUnmarshaller();
913 XMLStreamReader streamReader = XMLInputFactory.newInstance()
914 .createXMLStreamReader(in);
915 JAXBElement<JalviewUserColours> jbe = um.unmarshal(streamReader,
916 JalviewUserColours.class);
917 JalviewUserColours jucs = jbe.getValue();
919 // JalviewUserColours jucs = JalviewUserColours.unmarshal(in);
922 * load feature colours
924 for (int i = jucs.getColour().size() - 1; i >= 0; i--)
926 Colour newcol = jucs.getColour().get(i);
927 FeatureColourI colour = jalview.project.Jalview2XML
928 .parseColour(newcol);
929 fr.setColour(newcol.getName(), colour);
930 fr.setOrder(newcol.getName(), i / (float) jucs.getColour().size());
934 * load feature filters; loaded filters will replace any that are
935 * currently defined, other defined filters are left unchanged
937 for (int i = 0; i < jucs.getFilter().size(); i++)
939 Filter filterModel = jucs.getFilter().get(i);
940 String featureType = filterModel.getFeatureType();
941 FeatureMatcherSetI filter = jalview.project.Jalview2XML
942 .parseFilter(featureType, filterModel.getMatcherSet());
943 if (!filter.isEmpty())
945 fr.setFeatureFilter(featureType, filter);
950 * update feature settings table
955 Object[][] data = ((FeatureTableModel) table.getModel())
958 updateFeatureRenderer(data, false);
961 } catch (Exception ex)
963 System.out.println("Error loading User Colour File\n" + ex);
968 * Offers a file chooser dialog, and then saves the current feature colours
969 * and any filters to the selected file in XML format
973 JalviewFileChooser chooser = new JalviewFileChooser("fc",
974 SEQUENCE_FEATURE_COLOURS);
975 chooser.setFileView(new JalviewFileView());
976 chooser.setDialogTitle(
977 MessageManager.getString("label.save_feature_colours"));
978 chooser.setToolTipText(MessageManager.getString("action.save"));
979 int option = chooser.showSaveDialog(this);
980 if (option == JalviewFileChooser.APPROVE_OPTION)
982 File file = chooser.getSelectedFile();
988 * Saves feature colours and filters to the given file
994 JalviewUserColours ucs = new JalviewUserColours();
995 ucs.setSchemeName("Sequence Features");
998 PrintWriter out = new PrintWriter(new OutputStreamWriter(
999 new FileOutputStream(file), "UTF-8"));
1002 * sort feature types by colour order, from 0 (highest)
1005 Set<String> fr_colours = fr.getAllFeatureColours();
1006 String[] sortedTypes = fr_colours
1007 .toArray(new String[fr_colours.size()]);
1008 Arrays.sort(sortedTypes, new Comparator<String>()
1011 public int compare(String type1, String type2)
1013 return Float.compare(fr.getOrder(type1), fr.getOrder(type2));
1018 * save feature colours
1020 for (String featureType : sortedTypes)
1022 FeatureColourI fcol = fr.getFeatureStyle(featureType);
1023 Colour col = jalview.project.Jalview2XML.marshalColour(featureType,
1025 ucs.getColour().add(col);
1029 * save any feature filters
1031 for (String featureType : sortedTypes)
1033 FeatureMatcherSetI filter = fr.getFeatureFilter(featureType);
1034 if (filter != null && !filter.isEmpty())
1036 Iterator<FeatureMatcherI> iterator = filter.getMatchers().iterator();
1037 FeatureMatcherI firstMatcher = iterator.next();
1038 jalview.xml.binding.jalview.FeatureMatcherSet ms = jalview.project.Jalview2XML
1039 .marshalFilter(firstMatcher, iterator,
1041 Filter filterModel = new Filter();
1042 filterModel.setFeatureType(featureType);
1043 filterModel.setMatcherSet(ms);
1044 ucs.getFilter().add(filterModel);
1047 JAXBContext jaxbContext = JAXBContext
1048 .newInstance(JalviewUserColours.class);
1049 Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
1050 jaxbMarshaller.marshal(
1051 new ObjectFactory().createJalviewUserColours(ucs), out);
1053 // jaxbMarshaller.marshal(object, pout);
1054 // marshaller.marshal(object);
1057 // ucs.marshal(out);
1059 } catch (Exception ex)
1061 ex.printStackTrace();
1065 public void invertSelection()
1067 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1068 for (int i = 0; i < data.length; i++)
1070 data[i][SHOW_COLUMN] = !(Boolean) data[i][SHOW_COLUMN];
1072 updateFeatureRenderer(data, true);
1076 public void orderByAvWidth()
1078 if (table == null || table.getModel() == null)
1082 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1083 float[] width = new float[data.length];
1087 for (int i = 0; i < data.length; i++)
1089 awidth = typeWidth.get(data[i][TYPE_COLUMN]);
1092 width[i] = awidth[1] / awidth[0];// *awidth[0]*awidth[2]; - better
1093 // weight - but have to make per
1094 // sequence, too (awidth[2])
1095 // if (width[i]==1) // hack to distinguish single width sequences.
1106 boolean sort = false;
1107 for (int i = 0; i < width.length; i++)
1109 // awidth = (float[]) typeWidth.get(data[i][0]);
1112 width[i] = fr.getOrder(data[i][TYPE_COLUMN].toString());
1115 width[i] = fr.setOrder(data[i][TYPE_COLUMN].toString(),
1121 width[i] /= max; // normalize
1122 fr.setOrder(data[i][TYPE_COLUMN].toString(), width[i]); // store for later
1126 sort = sort || width[i - 1] > width[i];
1131 jalview.util.QuickSort.sort(width, data);
1132 // update global priority order
1135 updateFeatureRenderer(data, false);
1143 frame.setClosed(true);
1144 } catch (Exception exe)
1150 public void updateFeatureRenderer(Object[][] data)
1152 updateFeatureRenderer(data, true);
1156 * Update the priority order of features; only repaint if this changed the order
1157 * of visible features
1162 void updateFeatureRenderer(Object[][] data, boolean visibleNew)
1164 FeatureSettingsBean[] rowData = getTableAsBeans(data);
1166 if (fr.setFeaturePriority(rowData, visibleNew))
1168 af.alignPanel.paintAlignment(true, true);
1173 * Converts table data into an array of data beans
1175 private FeatureSettingsBean[] getTableAsBeans(Object[][] data)
1177 FeatureSettingsBean[] rowData = new FeatureSettingsBean[data.length];
1178 for (int i = 0; i < data.length; i++)
1180 String type = (String) data[i][TYPE_COLUMN];
1181 FeatureColourI colour = (FeatureColourI) data[i][COLOUR_COLUMN];
1182 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) data[i][FILTER_COLUMN];
1183 Boolean isShown = (Boolean) data[i][SHOW_COLUMN];
1184 rowData[i] = new FeatureSettingsBean(type, colour, theFilter,
1190 private void jbInit() throws Exception
1192 this.setLayout(new BorderLayout());
1194 JPanel settingsPane = new JPanel();
1195 settingsPane.setLayout(new BorderLayout());
1197 JPanel bigPanel = new JPanel();
1198 bigPanel.setLayout(new BorderLayout());
1200 groupPanel = new JPanel();
1201 bigPanel.add(groupPanel, BorderLayout.NORTH);
1203 JButton invert = new JButton(
1204 MessageManager.getString("label.invert_selection"));
1205 invert.setFont(JvSwingUtils.getLabelFont());
1206 invert.addActionListener(new ActionListener()
1209 public void actionPerformed(ActionEvent e)
1215 JButton optimizeOrder = new JButton(
1216 MessageManager.getString("label.optimise_order"));
1217 optimizeOrder.setFont(JvSwingUtils.getLabelFont());
1218 optimizeOrder.addActionListener(new ActionListener()
1221 public void actionPerformed(ActionEvent e)
1227 JButton sortByScore = new JButton(
1228 MessageManager.getString("label.seq_sort_by_score"));
1229 sortByScore.setFont(JvSwingUtils.getLabelFont());
1230 sortByScore.addActionListener(new ActionListener()
1233 public void actionPerformed(ActionEvent e)
1235 af.avc.sortAlignmentByFeatureScore(null);
1238 JButton sortByDens = new JButton(
1239 MessageManager.getString("label.sequence_sort_by_density"));
1240 sortByDens.setFont(JvSwingUtils.getLabelFont());
1241 sortByDens.addActionListener(new ActionListener()
1244 public void actionPerformed(ActionEvent e)
1246 af.avc.sortAlignmentByFeatureDensity(null);
1250 JButton help = new JButton(MessageManager.getString("action.help"));
1251 help.setFont(JvSwingUtils.getLabelFont());
1252 help.addActionListener(new ActionListener()
1255 public void actionPerformed(ActionEvent e)
1259 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1260 } catch (HelpSetException e1)
1262 e1.printStackTrace();
1266 help.setFont(JvSwingUtils.getLabelFont());
1267 help.setText(MessageManager.getString("action.help"));
1268 help.addActionListener(new ActionListener()
1271 public void actionPerformed(ActionEvent e)
1275 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1276 } catch (HelpSetException e1)
1278 e1.printStackTrace();
1283 JButton cancel = new JButton(MessageManager.getString("action.cancel"));
1284 cancel.setFont(JvSwingUtils.getLabelFont());
1285 cancel.addActionListener(new ActionListener()
1288 public void actionPerformed(ActionEvent e)
1290 fr.setTransparency(originalTransparency);
1291 fr.setFeatureFilters(originalFilters);
1292 updateFeatureRenderer(originalData);
1297 JButton ok = new JButton(MessageManager.getString("action.ok"));
1298 ok.setFont(JvSwingUtils.getLabelFont());
1299 ok.addActionListener(new ActionListener()
1302 public void actionPerformed(ActionEvent e)
1308 JButton loadColours = new JButton(
1309 MessageManager.getString("label.load_colours"));
1310 loadColours.setFont(JvSwingUtils.getLabelFont());
1311 loadColours.setToolTipText(
1312 MessageManager.getString("label.load_colours_tooltip"));
1313 loadColours.addActionListener(new ActionListener()
1316 public void actionPerformed(ActionEvent e)
1322 JButton saveColours = new JButton(
1323 MessageManager.getString("label.save_colours"));
1324 saveColours.setFont(JvSwingUtils.getLabelFont());
1325 saveColours.setToolTipText(
1326 MessageManager.getString("label.save_colours_tooltip"));
1327 saveColours.addActionListener(new ActionListener()
1330 public void actionPerformed(ActionEvent e)
1335 transparency.addChangeListener(new ChangeListener()
1338 public void stateChanged(ChangeEvent evt)
1340 if (!inConstruction)
1342 fr.setTransparency((100 - transparency.getValue()) / 100f);
1343 af.alignPanel.paintAlignment(true, true);
1348 transparency.setMaximum(70);
1349 transparency.setToolTipText(
1350 MessageManager.getString("label.transparency_tip"));
1352 JPanel transPanel = new JPanel(new GridLayout(1, 2));
1353 bigPanel.add(transPanel, BorderLayout.SOUTH);
1355 JPanel transbuttons = new JPanel(new GridLayout(5, 1));
1356 transbuttons.add(optimizeOrder);
1357 transbuttons.add(invert);
1358 transbuttons.add(sortByScore);
1359 transbuttons.add(sortByDens);
1360 transbuttons.add(help);
1361 transPanel.add(transparency);
1362 transPanel.add(transbuttons);
1364 JPanel buttonPanel = new JPanel();
1365 buttonPanel.add(ok);
1366 buttonPanel.add(cancel);
1367 buttonPanel.add(loadColours);
1368 buttonPanel.add(saveColours);
1369 bigPanel.add(scrollPane, BorderLayout.CENTER);
1370 settingsPane.add(bigPanel, BorderLayout.CENTER);
1371 settingsPane.add(buttonPanel, BorderLayout.SOUTH);
1372 this.add(settingsPane);
1376 * Answers a suitable tooltip to show on the colour cell of the table
1380 * if true include 'click to edit' and similar text
1383 public static String getColorTooltip(FeatureColourI fcol,
1390 if (fcol.isSimpleColour())
1392 return withHint ? BASE_TOOLTIP : null;
1394 String description = fcol.getDescription();
1395 description = description.replaceAll("<", "<");
1396 description = description.replaceAll(">", ">");
1397 StringBuilder tt = new StringBuilder(description);
1400 tt.append("<br>").append(BASE_TOOLTIP).append("</br>");
1402 return JvSwingUtils.wrapTooltip(true, tt.toString());
1405 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol,
1408 boolean thr = false;
1409 StringBuilder tx = new StringBuilder();
1411 if (gcol.isColourByAttribute())
1413 tx.append(FeatureMatcher
1414 .toAttributeDisplayName(gcol.getAttributeName()));
1416 else if (!gcol.isColourByLabel())
1418 tx.append(MessageManager.getString("label.score"));
1421 if (gcol.isAboveThreshold())
1426 if (gcol.isBelowThreshold())
1431 if (gcol.isColourByLabel())
1437 if (!gcol.isColourByAttribute())
1445 Color newColor = gcol.getMaxColour();
1446 comp.setBackground(newColor);
1447 // System.err.println("Width is " + w / 2);
1448 Icon ficon = new FeatureIcon(gcol, comp.getBackground(), w, h, thr);
1449 comp.setIcon(ficon);
1450 // tt+="RGB value: Max (" + newColor.getRed() + ", "
1451 // + newColor.getGreen() + ", " + newColor.getBlue()
1452 // + ")\nMin (" + minCol.getRed() + ", " + minCol.getGreen()
1453 // + ", " + minCol.getBlue() + ")");
1455 comp.setHorizontalAlignment(SwingConstants.CENTER);
1456 comp.setText(tx.toString());
1459 // ///////////////////////////////////////////////////////////////////////
1460 // http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
1461 // ///////////////////////////////////////////////////////////////////////
1462 class FeatureTableModel extends AbstractTableModel
1464 private String[] columnNames = {
1465 MessageManager.getString("label.feature_type"),
1466 MessageManager.getString("action.colour"),
1467 MessageManager.getString("label.filter"),
1468 MessageManager.getString("label.show") };
1470 private Object[][] data;
1472 FeatureTableModel(Object[][] data)
1477 public Object[][] getData()
1482 public void setData(Object[][] data)
1488 public int getColumnCount()
1490 return columnNames.length;
1493 public Object[] getRow(int row)
1499 public int getRowCount()
1505 public String getColumnName(int col)
1507 return columnNames[col];
1511 public Object getValueAt(int row, int col)
1513 return data[row][col];
1517 * Answers the class of the object in column c of the first row of the table
1520 public Class<?> getColumnClass(int c)
1522 Object v = getValueAt(0, c);
1523 return v == null ? null : v.getClass();
1527 public boolean isCellEditable(int row, int col)
1529 return col == 0 ? false : true;
1533 public void setValueAt(Object value, int row, int col)
1535 data[row][col] = value;
1536 fireTableCellUpdated(row, col);
1537 updateFeatureRenderer(data);
1542 class ColorRenderer extends JLabel implements TableCellRenderer
1544 Border unselectedBorder = null;
1546 Border selectedBorder = null;
1548 public ColorRenderer()
1550 setOpaque(true); // MUST do this for background to show up.
1551 setHorizontalTextPosition(SwingConstants.CENTER);
1552 setVerticalTextPosition(SwingConstants.CENTER);
1556 public Component getTableCellRendererComponent(JTable tbl, Object color,
1557 boolean isSelected, boolean hasFocus, int row, int column)
1559 FeatureColourI cellColour = (FeatureColourI) color;
1561 setBackground(tbl.getBackground());
1562 if (!cellColour.isSimpleColour())
1564 Rectangle cr = tbl.getCellRect(row, column, false);
1565 FeatureSettings.renderGraduatedColor(this, cellColour,
1566 (int) cr.getWidth(), (int) cr.getHeight());
1572 setBackground(cellColour.getColour());
1576 if (selectedBorder == null)
1578 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1579 tbl.getSelectionBackground());
1581 setBorder(selectedBorder);
1585 if (unselectedBorder == null)
1587 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1588 tbl.getBackground());
1590 setBorder(unselectedBorder);
1597 class FilterRenderer extends JLabel implements TableCellRenderer
1599 javax.swing.border.Border unselectedBorder = null;
1601 javax.swing.border.Border selectedBorder = null;
1603 public FilterRenderer()
1605 setOpaque(true); // MUST do this for background to show up.
1606 setHorizontalTextPosition(SwingConstants.CENTER);
1607 setVerticalTextPosition(SwingConstants.CENTER);
1611 public Component getTableCellRendererComponent(JTable tbl,
1612 Object filter, boolean isSelected, boolean hasFocus, int row,
1615 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) filter;
1617 String asText = theFilter.toString();
1618 setBackground(tbl.getBackground());
1619 this.setText(asText);
1624 if (selectedBorder == null)
1626 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1627 tbl.getSelectionBackground());
1629 setBorder(selectedBorder);
1633 if (unselectedBorder == null)
1635 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1636 tbl.getBackground());
1638 setBorder(unselectedBorder);
1646 * update comp using rendering settings from gcol
1651 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol)
1653 int w = comp.getWidth(), h = comp.getHeight();
1656 w = (int) comp.getPreferredSize().getWidth();
1657 h = (int) comp.getPreferredSize().getHeight();
1664 renderGraduatedColor(comp, gcol, w, h);
1667 class ColorEditor extends AbstractCellEditor
1668 implements TableCellEditor, ActionListener
1672 FeatureColourI currentColor;
1674 FeatureTypeSettings chooser;
1680 protected static final String EDIT = "edit";
1682 int rowSelected = 0;
1684 public ColorEditor(FeatureSettings fs)
1687 // Set up the editor (from the table's point of view),
1688 // which is a button.
1689 // This button brings up the color chooser dialog,
1690 // which is the editor from the user's point of view.
1691 button = new JButton();
1692 button.setActionCommand(EDIT);
1693 button.addActionListener(this);
1694 button.setBorderPainted(false);
1698 * Handles events from the editor button, and from the colour/filters
1699 * dialog's OK button
1702 public void actionPerformed(ActionEvent e)
1704 if (button == e.getSource())
1706 if (currentColor.isSimpleColour())
1709 * simple colour chooser
1711 String ttl = MessageManager.getString("label.select_colour");
1712 ColourChooserListener listener = new ColourChooserListener() {
1714 public void colourSelected(Color c)
1716 currentColor = new FeatureColour(c);
1717 me.table.setValueAt(currentColor, rowSelected, COLOUR_COLUMN);
1720 JalviewColourChooser.showColourChooser(button, ttl, currentColor.getColour(), listener);
1725 * variable colour and filters dialog
1727 chooser = new FeatureTypeSettings(me.fr, type);
1732 chooser.setRequestFocusEnabled(true);
1733 chooser.requestFocus();
1735 chooser.addActionListener(this);
1736 // Make the renderer reappear.
1737 fireEditingStopped();
1743 * after OK in variable colour dialog, any changes to colour
1744 * (or filters!) are already set in FeatureRenderer, so just
1745 * update table data without triggering updateFeatureRenderer
1747 currentColor = fr.getFeatureColours().get(type);
1748 FeatureMatcherSetI currentFilter = me.fr.getFeatureFilter(type);
1749 if (currentFilter == null)
1751 currentFilter = new FeatureMatcherSet();
1753 Object[] data = ((FeatureTableModel) table.getModel())
1754 .getData()[rowSelected];
1755 data[COLOUR_COLUMN] = currentColor;
1756 data[FILTER_COLUMN] = currentFilter;
1758 fireEditingStopped();
1759 me.table.validate();
1763 // Implement the one CellEditor method that AbstractCellEditor doesn't.
1765 public Object getCellEditorValue()
1767 return currentColor;
1770 // Implement the one method defined by TableCellEditor.
1772 public Component getTableCellEditorComponent(JTable theTable, Object value,
1773 boolean isSelected, int row, int column)
1775 currentColor = (FeatureColourI) value;
1776 this.rowSelected = row;
1777 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1778 button.setOpaque(true);
1779 button.setBackground(me.getBackground());
1780 if (!currentColor.isSimpleColour())
1782 JLabel btn = new JLabel();
1783 btn.setSize(button.getSize());
1784 FeatureSettings.renderGraduatedColor(btn, currentColor);
1785 button.setBackground(btn.getBackground());
1786 button.setIcon(btn.getIcon());
1787 button.setText(btn.getText());
1792 button.setIcon(null);
1793 button.setBackground(currentColor.getColour());
1800 * The cell editor for the Filter column. It displays the text of any filters
1801 * for the feature type in that row (in full as a tooltip, possible abbreviated
1802 * as display text). On click in the cell, opens the Feature Display Settings
1803 * dialog at the Filters tab.
1805 class FilterEditor extends AbstractCellEditor
1806 implements TableCellEditor, ActionListener
1810 FeatureMatcherSetI currentFilter;
1818 protected static final String EDIT = "edit";
1820 int rowSelected = 0;
1822 public FilterEditor(FeatureSettings me)
1825 button = new JButton();
1826 button.setActionCommand(EDIT);
1827 button.addActionListener(this);
1828 button.setBorderPainted(false);
1832 * Handles events from the editor button
1835 public void actionPerformed(ActionEvent e)
1837 if (button == e.getSource())
1839 FeatureTypeSettings chooser = new FeatureTypeSettings(me.fr, type);
1840 chooser.addActionListener(this);
1841 chooser.setRequestFocusEnabled(true);
1842 chooser.requestFocus();
1843 if (lastLocation != null)
1845 // todo open at its last position on screen
1846 chooser.setBounds(lastLocation.x, lastLocation.y,
1847 chooser.getWidth(), chooser.getHeight());
1850 fireEditingStopped();
1852 else if (e.getSource() instanceof Component)
1856 * after OK in variable colour dialog, any changes to filter
1857 * (or colours!) are already set in FeatureRenderer, so just
1858 * update table data without triggering updateFeatureRenderer
1860 FeatureColourI currentColor = fr.getFeatureColours().get(type);
1861 currentFilter = me.fr.getFeatureFilter(type);
1862 if (currentFilter == null)
1864 currentFilter = new FeatureMatcherSet();
1866 Object[] data = ((FeatureTableModel) table.getModel())
1867 .getData()[rowSelected];
1868 data[COLOUR_COLUMN] = currentColor;
1869 data[FILTER_COLUMN] = currentFilter;
1870 fireEditingStopped();
1871 me.table.validate();
1876 public Object getCellEditorValue()
1878 return currentFilter;
1882 public Component getTableCellEditorComponent(JTable theTable, Object value,
1883 boolean isSelected, int row, int column)
1885 currentFilter = (FeatureMatcherSetI) value;
1886 this.rowSelected = row;
1887 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1888 button.setOpaque(true);
1889 button.setBackground(me.getBackground());
1890 button.setText(currentFilter.toString());
1891 button.setIcon(null);
1897 class FeatureIcon implements Icon
1899 FeatureColourI gcol;
1903 boolean midspace = false;
1905 int width = 50, height = 20;
1907 int s1, e1; // start and end of midpoint band for thresholded symbol
1909 Color mpcolour = Color.white;
1911 FeatureIcon(FeatureColourI gfc, Color bg, int w, int h, boolean mspace)
1931 public int getIconWidth()
1937 public int getIconHeight()
1943 public void paintIcon(Component c, Graphics g, int x, int y)
1946 if (gcol.isColourByLabel())
1949 g.fillRect(0, 0, width, height);
1950 // need an icon here.
1951 g.setColor(gcol.getMaxColour());
1953 g.setFont(new Font("Verdana", Font.PLAIN, 9));
1955 // g.setFont(g.getFont().deriveFont(
1956 // AffineTransform.getScaleInstance(
1957 // width/g.getFontMetrics().stringWidth("Label"),
1958 // height/g.getFontMetrics().getHeight())));
1960 g.drawString(MessageManager.getString("label.label"), 0, 0);
1965 Color minCol = gcol.getMinColour();
1967 g.fillRect(0, 0, s1, height);
1970 g.setColor(Color.white);
1971 g.fillRect(s1, 0, e1 - s1, height);
1973 g.setColor(gcol.getMaxColour());
1974 g.fillRect(0, e1, width - e1, height);