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.FeatureMatcherI;
28 import jalview.datamodel.features.FeatureMatcherSet;
29 import jalview.datamodel.features.FeatureMatcherSetI;
30 import jalview.gui.Help.HelpId;
31 import jalview.io.JalviewFileChooser;
32 import jalview.io.JalviewFileView;
33 import jalview.schemes.FeatureColour;
34 import jalview.util.MessageManager;
35 import jalview.util.Platform;
36 import jalview.viewmodel.seqfeatures.FeatureRendererModel.FeatureSettingsBean;
37 import jalview.xml.binding.jalview.JalviewUserColours;
38 import jalview.xml.binding.jalview.JalviewUserColours.Colour;
39 import jalview.xml.binding.jalview.JalviewUserColours.Filter;
40 import jalview.xml.binding.jalview.ObjectFactory;
42 import java.awt.BorderLayout;
43 import java.awt.Color;
44 import java.awt.Component;
45 import java.awt.Dimension;
47 import java.awt.Graphics;
48 import java.awt.GridLayout;
49 import java.awt.Point;
50 import java.awt.Rectangle;
51 import java.awt.event.ActionEvent;
52 import java.awt.event.ActionListener;
53 import java.awt.event.ItemEvent;
54 import java.awt.event.ItemListener;
55 import java.awt.event.MouseAdapter;
56 import java.awt.event.MouseEvent;
57 import java.awt.event.MouseMotionAdapter;
58 import java.beans.PropertyChangeEvent;
59 import java.beans.PropertyChangeListener;
61 import java.io.FileInputStream;
62 import java.io.FileOutputStream;
63 import java.io.InputStreamReader;
64 import java.io.OutputStreamWriter;
65 import java.io.PrintWriter;
66 import java.util.Arrays;
67 import java.util.Comparator;
68 import java.util.HashMap;
69 import java.util.HashSet;
70 import java.util.Hashtable;
71 import java.util.Iterator;
72 import java.util.List;
76 import javax.help.HelpSetException;
77 import javax.swing.AbstractCellEditor;
78 import javax.swing.BorderFactory;
79 import javax.swing.Icon;
80 import javax.swing.JButton;
81 import javax.swing.JCheckBox;
82 import javax.swing.JCheckBoxMenuItem;
83 import javax.swing.JColorChooser;
84 import javax.swing.JDialog;
85 import javax.swing.JInternalFrame;
86 import javax.swing.JLabel;
87 import javax.swing.JLayeredPane;
88 import javax.swing.JMenuItem;
89 import javax.swing.JPanel;
90 import javax.swing.JPopupMenu;
91 import javax.swing.JScrollPane;
92 import javax.swing.JSlider;
93 import javax.swing.JTable;
94 import javax.swing.ListSelectionModel;
95 import javax.swing.SwingConstants;
96 import javax.swing.event.ChangeEvent;
97 import javax.swing.event.ChangeListener;
98 import javax.swing.table.AbstractTableModel;
99 import javax.swing.table.TableCellEditor;
100 import javax.swing.table.TableCellRenderer;
101 import javax.swing.table.TableColumn;
102 import javax.xml.bind.JAXBContext;
103 import javax.xml.bind.JAXBElement;
104 import javax.xml.bind.Marshaller;
105 import javax.xml.stream.XMLInputFactory;
106 import javax.xml.stream.XMLStreamReader;
108 public class FeatureSettings extends JPanel
109 implements FeatureSettingsControllerI
111 private static final String SEQUENCE_FEATURE_COLOURS = MessageManager
112 .getString("label.sequence_feature_colours");
115 * column indices of fields in Feature Settings table
117 static final int TYPE_COLUMN = 0;
119 static final int COLOUR_COLUMN = 1;
121 static final int FILTER_COLUMN = 2;
123 static final int SHOW_COLUMN = 3;
125 private static final int COLUMN_COUNT = 4;
127 private static final int MIN_WIDTH = 400;
129 private static final int MIN_HEIGHT = 400;
131 final FeatureRenderer fr;
133 public final AlignFrame af;
136 * 'original' fields hold settings to restore on Cancel
138 Object[][] originalData;
140 private float originalTransparency;
142 private Map<String, FeatureMatcherSetI> originalFilters;
144 final JInternalFrame frame;
146 JScrollPane scrollPane = new JScrollPane();
152 JSlider transparency = new JSlider();
155 * when true, constructor is still executing - so ignore UI events
157 protected volatile boolean inConstruction = true;
159 int selectedRow = -1;
161 JButton fetchDAS = new JButton();
163 JButton saveDAS = new JButton();
165 JButton cancelDAS = new JButton();
167 boolean resettingTable = false;
170 * true when Feature Settings are updating from feature renderer
172 private boolean handlingUpdate = false;
175 * holds {featureCount, totalExtent} for each feature type
177 Map<String, float[]> typeWidth = null;
184 public FeatureSettings(AlignFrame alignFrame)
186 this.af = alignFrame;
187 fr = af.getFeatureRenderer();
189 // save transparency for restore on Cancel
190 originalTransparency = fr.getTransparency();
191 int originalTransparencyAsPercent = (int) (originalTransparency * 100);
192 transparency.setMaximum(100 - originalTransparencyAsPercent);
194 originalFilters = new HashMap<>(fr.getFeatureFilters()); // shallow copy
199 } catch (Exception ex)
201 ex.printStackTrace();
207 public String getToolTipText(MouseEvent e)
210 int column = table.columnAtPoint(e.getPoint());
214 tip = JvSwingUtils.wrapTooltip(true, MessageManager
215 .getString("label.feature_settings_click_drag"));
218 int row = table.rowAtPoint(e.getPoint());
219 FeatureMatcherSet o = (FeatureMatcherSet) table.getValueAt(row,
222 ? MessageManager.getString("label.filters_tooltip")
231 table.getTableHeader().setFont(new Font("Verdana", Font.PLAIN, 12));
232 table.setFont(new Font("Verdana", Font.PLAIN, 12));
234 // table.setDefaultRenderer(Color.class, new ColorRenderer());
235 // table.setDefaultEditor(Color.class, new ColorEditor(this));
237 table.setDefaultEditor(FeatureColour.class, new ColorEditor(this));
238 table.setDefaultRenderer(FeatureColour.class, new ColorRenderer());
240 table.setDefaultEditor(FeatureMatcherSet.class, new FilterEditor(this));
241 table.setDefaultRenderer(FeatureMatcherSet.class, new FilterRenderer());
243 TableColumn colourColumn = new TableColumn(COLOUR_COLUMN, 75,
244 new ColorRenderer(), new ColorEditor(this));
245 table.addColumn(colourColumn);
247 TableColumn filterColumn = new TableColumn(FILTER_COLUMN, 75,
248 new FilterRenderer(), new FilterEditor(this));
249 table.addColumn(filterColumn);
251 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
253 table.addMouseListener(new MouseAdapter()
256 public void mousePressed(MouseEvent evt)
258 selectedRow = table.rowAtPoint(evt.getPoint());
259 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
260 if (evt.isPopupTrigger())
262 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
263 popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
266 else if (evt.getClickCount() == 2)
268 boolean invertSelection = evt.isAltDown();
269 boolean toggleSelection = Platform.isControlDown(evt);
270 boolean extendSelection = evt.isShiftDown();
271 fr.ap.alignFrame.avc.markColumnsContainingFeatures(
272 invertSelection, extendSelection, toggleSelection, type);
276 // isPopupTrigger fires on mouseReleased on Windows
278 public void mouseReleased(MouseEvent evt)
280 selectedRow = table.rowAtPoint(evt.getPoint());
281 if (evt.isPopupTrigger())
283 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
284 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
285 popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
291 table.addMouseMotionListener(new MouseMotionAdapter()
294 public void mouseDragged(MouseEvent evt)
296 int newRow = table.rowAtPoint(evt.getPoint());
297 if (newRow != selectedRow && selectedRow != -1 && newRow != -1)
300 * reposition 'selectedRow' to 'newRow' (the dragged to location)
301 * this could be more than one row away for a very fast drag action
302 * so just swap it with adjacent rows until we get it there
304 Object[][] data = ((FeatureTableModel) table.getModel())
306 int direction = newRow < selectedRow ? -1 : 1;
307 for (int i = selectedRow; i != newRow; i += direction)
309 Object[] temp = data[i];
310 data[i] = data[i + direction];
311 data[i + direction] = temp;
313 updateFeatureRenderer(data);
315 selectedRow = newRow;
319 // table.setToolTipText(JvSwingUtils.wrapTooltip(true,
320 // MessageManager.getString("label.feature_settings_click_drag")));
321 scrollPane.setViewportView(table);
323 if (af.getViewport().isShowSequenceFeatures() || !fr.hasRenderOrder())
325 fr.findAllFeatures(true); // display everything!
328 discoverAllFeatureData();
329 final PropertyChangeListener change;
330 final FeatureSettings fs = this;
331 fr.addPropertyChangeListener(change = new PropertyChangeListener()
334 public void propertyChange(PropertyChangeEvent evt)
336 if (!fs.resettingTable && !fs.handlingUpdate)
338 fs.handlingUpdate = true;
340 // new groups may be added with new sequence feature types only
341 fs.handlingUpdate = false;
347 frame = new JInternalFrame();
348 frame.setContentPane(this);
349 if (Platform.isAMac())
351 Desktop.addInternalFrame(frame,
352 MessageManager.getString("label.sequence_feature_settings"),
357 Desktop.addInternalFrame(frame,
358 MessageManager.getString("label.sequence_feature_settings"),
361 frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
363 frame.addInternalFrameListener(
364 new javax.swing.event.InternalFrameAdapter()
367 public void internalFrameClosed(
368 javax.swing.event.InternalFrameEvent evt)
370 fr.removePropertyChangeListener(change);
373 frame.setLayer(JLayeredPane.PALETTE_LAYER);
374 inConstruction = false;
377 protected void popupSort(final int rowSelected, final String type,
378 final Object typeCol, final Map<String, float[][]> minmax, int x,
381 final FeatureColourI featureColour = (FeatureColourI) typeCol;
383 JPopupMenu men = new JPopupMenu(MessageManager
384 .formatMessage("label.settings_for_param", new String[]
386 JMenuItem scr = new JMenuItem(
387 MessageManager.getString("label.sort_by_score"));
389 final FeatureSettings me = this;
390 scr.addActionListener(new ActionListener()
394 public void actionPerformed(ActionEvent e)
397 .sortAlignmentByFeatureScore(Arrays.asList(new String[]
402 JMenuItem dens = new JMenuItem(
403 MessageManager.getString("label.sort_by_density"));
404 dens.addActionListener(new ActionListener()
408 public void actionPerformed(ActionEvent e)
411 .sortAlignmentByFeatureDensity(Arrays.asList(new String[]
419 * variable colour options include colour by label, by score,
420 * by selected attribute text, or attribute value
422 final JCheckBoxMenuItem mxcol = new JCheckBoxMenuItem(
423 MessageManager.getString("label.variable_colour"));
424 mxcol.setSelected(!featureColour.isSimpleColour());
426 mxcol.addActionListener(new ActionListener()
428 JColorChooser colorChooser;
431 public void actionPerformed(ActionEvent e)
433 if (e.getSource() == mxcol)
435 if (featureColour.isSimpleColour())
437 FeatureTypeSettings fc = new FeatureTypeSettings(me.fr, type);
438 fc.addActionListener(this);
442 // bring up simple color chooser
443 colorChooser = new JColorChooser();
444 String title = MessageManager
445 .getString("label.select_colour");
446 JDialog dialog = JColorChooser.createDialog(me,
447 title, true, // modal
448 colorChooser, this, // OK button handler
449 null); // no CANCEL button handler
450 colorChooser.setColor(featureColour.getMaxColour());
451 dialog.setVisible(true);
456 if (e.getSource() instanceof FeatureTypeSettings)
459 * update after OK in feature colour dialog; the updated
460 * colour will have already been set in the FeatureRenderer
462 FeatureColourI fci = fr.getFeatureColours().get(type);
463 table.setValueAt(fci, rowSelected, 1);
468 // probably the color chooser!
469 table.setValueAt(new FeatureColour(colorChooser.getColor()),
472 me.updateFeatureRenderer(
473 ((FeatureTableModel) table.getModel()).getData(),
481 JMenuItem selCols = new JMenuItem(
482 MessageManager.getString("label.select_columns_containing"));
483 selCols.addActionListener(new ActionListener()
486 public void actionPerformed(ActionEvent arg0)
488 fr.ap.alignFrame.avc.markColumnsContainingFeatures(false, false,
492 JMenuItem clearCols = new JMenuItem(MessageManager
493 .getString("label.select_columns_not_containing"));
494 clearCols.addActionListener(new ActionListener()
497 public void actionPerformed(ActionEvent arg0)
499 fr.ap.alignFrame.avc.markColumnsContainingFeatures(true, false,
503 JMenuItem hideCols = new JMenuItem(
504 MessageManager.getString("label.hide_columns_containing"));
505 hideCols.addActionListener(new ActionListener()
508 public void actionPerformed(ActionEvent arg0)
510 fr.ap.alignFrame.hideFeatureColumns(type, true);
513 JMenuItem hideOtherCols = new JMenuItem(
514 MessageManager.getString("label.hide_columns_not_containing"));
515 hideOtherCols.addActionListener(new ActionListener()
518 public void actionPerformed(ActionEvent arg0)
520 fr.ap.alignFrame.hideFeatureColumns(type, false);
526 men.add(hideOtherCols);
527 men.show(table, x, y);
531 synchronized public void discoverAllFeatureData()
533 Set<String> allGroups = new HashSet<>();
534 AlignmentI alignment = af.getViewport().getAlignment();
536 for (int i = 0; i < alignment.getHeight(); i++)
538 SequenceI seq = alignment.getSequenceAt(i);
539 for (String group : seq.getFeatures().getFeatureGroups(true))
541 if (group != null && !allGroups.contains(group))
543 allGroups.add(group);
544 checkGroupState(group);
555 * Synchronise gui group list and check visibility of group
558 * @return true if group is visible
560 private boolean checkGroupState(String group)
562 boolean visible = fr.checkGroupVisibility(group, true);
564 for (int g = 0; g < groupPanel.getComponentCount(); g++)
566 if (((JCheckBox) groupPanel.getComponent(g)).getText().equals(group))
568 ((JCheckBox) groupPanel.getComponent(g)).setSelected(visible);
573 final String grp = group;
574 final JCheckBox check = new JCheckBox(group, visible);
575 check.setFont(new Font("Serif", Font.BOLD, 12));
576 check.setToolTipText(group);
577 check.addItemListener(new ItemListener()
580 public void itemStateChanged(ItemEvent evt)
582 fr.setGroupVisibility(check.getText(), check.isSelected());
583 resetTable(new String[] { grp });
584 af.alignPanel.paintAlignment(true, true);
587 groupPanel.add(check);
591 synchronized void resetTable(String[] groupChanged)
597 resettingTable = true;
598 typeWidth = new Hashtable<>();
599 // TODO: change avWidth calculation to 'per-sequence' average and use long
602 Set<String> displayableTypes = new HashSet<>();
603 Set<String> foundGroups = new HashSet<>();
606 * determine which feature types may be visible depending on
607 * which groups are selected, and recompute average width data
609 for (int i = 0; i < af.getViewport().getAlignment().getHeight(); i++)
612 SequenceI seq = af.getViewport().getAlignment().getSequenceAt(i);
615 * get the sequence's groups for positional features
616 * and keep track of which groups are visible
618 Set<String> groups = seq.getFeatures().getFeatureGroups(true);
619 Set<String> visibleGroups = new HashSet<>();
620 for (String group : groups)
622 if (group == null || checkGroupState(group))
624 visibleGroups.add(group);
627 foundGroups.addAll(groups);
630 * get distinct feature types for visible groups
631 * record distinct visible types, and their count and total length
633 Set<String> types = seq.getFeatures().getFeatureTypesForGroups(true,
634 visibleGroups.toArray(new String[visibleGroups.size()]));
635 for (String type : types)
637 displayableTypes.add(type);
638 float[] avWidth = typeWidth.get(type);
641 avWidth = new float[2];
642 typeWidth.put(type, avWidth);
644 // todo this could include features with a non-visible group
645 // - do we greatly care?
646 // todo should we include non-displayable features here, and only
647 // update when features are added?
648 avWidth[0] += seq.getFeatures().getFeatureCount(true, type);
649 avWidth[1] += seq.getFeatures().getTotalFeatureLength(type);
653 Object[][] data = new Object[displayableTypes.size()][COLUMN_COUNT];
656 if (fr.hasRenderOrder())
660 fr.findAllFeatures(groupChanged != null); // prod to update
661 // colourschemes. but don't
663 // First add the checks in the previous render order,
664 // in case the window has been closed and reopened
666 List<String> frl = fr.getRenderOrder();
667 for (int ro = frl.size() - 1; ro > -1; ro--)
669 String type = frl.get(ro);
671 if (!displayableTypes.contains(type))
676 data[dataIndex][TYPE_COLUMN] = type;
677 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
678 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
679 data[dataIndex][FILTER_COLUMN] = featureFilter == null
680 ? new FeatureMatcherSet()
682 data[dataIndex][SHOW_COLUMN] = new Boolean(
683 af.getViewport().getFeaturesDisplayed().isVisible(type));
685 displayableTypes.remove(type);
690 * process any extra features belonging only to
691 * a group which was just selected
693 while (!displayableTypes.isEmpty())
695 String type = displayableTypes.iterator().next();
696 data[dataIndex][TYPE_COLUMN] = type;
698 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
699 if (data[dataIndex][COLOUR_COLUMN] == null)
701 // "Colour has been updated in another view!!"
702 fr.clearRenderOrder();
705 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
706 data[dataIndex][FILTER_COLUMN] = featureFilter == null
707 ? new FeatureMatcherSet()
709 data[dataIndex][SHOW_COLUMN] = new Boolean(true);
711 displayableTypes.remove(type);
714 if (originalData == null)
716 originalData = new Object[data.length][COLUMN_COUNT];
717 for (int i = 0; i < data.length; i++)
719 System.arraycopy(data[i], 0, originalData[i], 0, COLUMN_COUNT);
724 updateOriginalData(data);
727 table.setModel(new FeatureTableModel(data));
728 table.getColumnModel().getColumn(0).setPreferredWidth(200);
730 groupPanel.setLayout(
731 new GridLayout(fr.getFeatureGroupsSize() / 4 + 1, 4));
732 pruneGroups(foundGroups);
733 groupPanel.validate();
735 updateFeatureRenderer(data, groupChanged != null);
736 resettingTable = false;
740 * Updates 'originalData' (used for restore on Cancel) if we detect that changes
741 * have been made outwith this dialog
743 * <li>a new feature type added (and made visible)</li>
744 * <li>a feature colour changed (in the Amend Features dialog)</li>
749 protected void updateOriginalData(Object[][] foundData)
751 // todo LinkedHashMap instead of Object[][] would be nice
753 Object[][] currentData = ((FeatureTableModel) table.getModel())
755 for (Object[] row : foundData)
757 String type = (String) row[TYPE_COLUMN];
758 boolean found = false;
759 for (Object[] current : currentData)
761 if (type.equals(current[TYPE_COLUMN]))
765 * currently dependent on object equality here;
766 * really need an equals method on FeatureColour
768 if (!row[COLOUR_COLUMN].equals(current[COLOUR_COLUMN]))
771 * feature colour has changed externally - update originalData
773 for (Object[] original : originalData)
775 if (type.equals(original[TYPE_COLUMN]))
777 original[COLOUR_COLUMN] = row[COLOUR_COLUMN];
788 * new feature detected - add to original data (on top)
790 Object[][] newData = new Object[originalData.length
792 for (int i = 0; i < originalData.length; i++)
794 System.arraycopy(originalData[i], 0, newData[i + 1], 0,
798 originalData = newData;
804 * Remove from the groups panel any checkboxes for groups that are not in the
805 * foundGroups set. This enables removing a group from the display when the last
806 * feature in that group is deleted.
810 protected void pruneGroups(Set<String> foundGroups)
812 for (int g = 0; g < groupPanel.getComponentCount(); g++)
814 JCheckBox checkbox = (JCheckBox) groupPanel.getComponent(g);
815 if (!foundGroups.contains(checkbox.getText()))
817 groupPanel.remove(checkbox);
823 * reorder data based on the featureRenderers global priority list.
827 private void ensureOrder(Object[][] data)
829 boolean sort = false;
830 float[] order = new float[data.length];
831 for (int i = 0; i < order.length; i++)
833 order[i] = fr.getOrder(data[i][0].toString());
836 order[i] = fr.setOrder(data[i][0].toString(), i / order.length);
840 sort = sort || order[i - 1] > order[i];
845 jalview.util.QuickSort.sort(order, data);
850 * Offers a file chooser dialog, and then loads the feature colours and
851 * filters from file in XML format and unmarshals to Jalview feature settings
855 JalviewFileChooser chooser = new JalviewFileChooser("fc",
856 SEQUENCE_FEATURE_COLOURS);
857 chooser.setFileView(new JalviewFileView());
858 chooser.setDialogTitle(
859 MessageManager.getString("label.load_feature_colours"));
860 chooser.setToolTipText(MessageManager.getString("action.load"));
862 int value = chooser.showOpenDialog(this);
864 if (value == JalviewFileChooser.APPROVE_OPTION)
866 File file = chooser.getSelectedFile();
872 * Loads feature colours and filters from XML stored in the given file
880 InputStreamReader in = new InputStreamReader(
881 new FileInputStream(file), "UTF-8");
883 JAXBContext jc = JAXBContext
884 .newInstance("jalview.xml.binding.jalview");
885 javax.xml.bind.Unmarshaller um = jc.createUnmarshaller();
886 XMLStreamReader streamReader = XMLInputFactory.newInstance()
887 .createXMLStreamReader(in);
888 JAXBElement<JalviewUserColours> jbe = um.unmarshal(streamReader,
889 JalviewUserColours.class);
890 JalviewUserColours jucs = jbe.getValue();
892 // JalviewUserColours jucs = JalviewUserColours.unmarshal(in);
895 * load feature colours
897 for (int i = jucs.getColour().size() - 1; i >= 0; i--)
899 Colour newcol = jucs.getColour().get(i);
900 FeatureColourI colour = jalview.project.Jalview2XML
901 .parseColour(newcol);
902 fr.setColour(newcol.getName(), colour);
903 fr.setOrder(newcol.getName(), i / (float) jucs.getColour().size());
907 * load feature filters; loaded filters will replace any that are
908 * currently defined, other defined filters are left unchanged
910 for (int i = 0; i < jucs.getFilter().size(); i++)
912 Filter filterModel = jucs.getFilter().get(i);
913 String featureType = filterModel.getFeatureType();
914 FeatureMatcherSetI filter = jalview.project.Jalview2XML
915 .parseFilter(featureType, filterModel.getMatcherSet());
916 if (!filter.isEmpty())
918 fr.setFeatureFilter(featureType, filter);
923 * update feature settings table
928 Object[][] data = ((FeatureTableModel) table.getModel())
931 updateFeatureRenderer(data, false);
934 } catch (Exception ex)
936 System.out.println("Error loading User Colour File\n" + ex);
941 * Offers a file chooser dialog, and then saves the current feature colours
942 * and any filters to the selected file in XML format
946 JalviewFileChooser chooser = new JalviewFileChooser("fc",
947 SEQUENCE_FEATURE_COLOURS);
948 chooser.setFileView(new JalviewFileView());
949 chooser.setDialogTitle(
950 MessageManager.getString("label.save_feature_colours"));
951 chooser.setToolTipText(MessageManager.getString("action.save"));
953 int value = chooser.showSaveDialog(this);
955 if (value == JalviewFileChooser.APPROVE_OPTION)
957 save(chooser.getSelectedFile());
962 * Saves feature colours and filters to the given file
968 JalviewUserColours ucs = new JalviewUserColours();
969 ucs.setSchemeName("Sequence Features");
972 PrintWriter out = new PrintWriter(new OutputStreamWriter(
973 new FileOutputStream(file), "UTF-8"));
976 * sort feature types by colour order, from 0 (highest)
979 Set<String> fr_colours = fr.getAllFeatureColours();
980 String[] sortedTypes = fr_colours
981 .toArray(new String[fr_colours.size()]);
982 Arrays.sort(sortedTypes, new Comparator<String>()
985 public int compare(String type1, String type2)
987 return Float.compare(fr.getOrder(type1), fr.getOrder(type2));
992 * save feature colours
994 for (String featureType : sortedTypes)
996 FeatureColourI fcol = fr.getFeatureStyle(featureType);
997 Colour col = jalview.project.Jalview2XML.marshalColour(featureType,
999 ucs.getColour().add(col);
1003 * save any feature filters
1005 for (String featureType : sortedTypes)
1007 FeatureMatcherSetI filter = fr.getFeatureFilter(featureType);
1008 if (filter != null && !filter.isEmpty())
1010 Iterator<FeatureMatcherI> iterator = filter.getMatchers().iterator();
1011 FeatureMatcherI firstMatcher = iterator.next();
1012 jalview.xml.binding.jalview.FeatureMatcherSet ms = jalview.project.Jalview2XML
1013 .marshalFilter(firstMatcher, iterator,
1015 Filter filterModel = new Filter();
1016 filterModel.setFeatureType(featureType);
1017 filterModel.setMatcherSet(ms);
1018 ucs.getFilter().add(filterModel);
1021 JAXBContext jaxbContext = JAXBContext
1022 .newInstance(JalviewUserColours.class);
1023 Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
1024 jaxbMarshaller.marshal(
1025 new ObjectFactory().createJalviewUserColours(ucs), out);
1027 // jaxbMarshaller.marshal(object, pout);
1028 // marshaller.marshal(object);
1031 // ucs.marshal(out);
1033 } catch (Exception ex)
1035 ex.printStackTrace();
1039 public void invertSelection()
1041 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1042 for (int i = 0; i < data.length; i++)
1044 data[i][SHOW_COLUMN] = !(Boolean) data[i][SHOW_COLUMN];
1046 updateFeatureRenderer(data, true);
1050 public void orderByAvWidth()
1052 if (table == null || table.getModel() == null)
1056 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
1057 float[] width = new float[data.length];
1061 for (int i = 0; i < data.length; i++)
1063 awidth = typeWidth.get(data[i][TYPE_COLUMN]);
1066 width[i] = awidth[1] / awidth[0];// *awidth[0]*awidth[2]; - better
1067 // weight - but have to make per
1068 // sequence, too (awidth[2])
1069 // if (width[i]==1) // hack to distinguish single width sequences.
1080 boolean sort = false;
1081 for (int i = 0; i < width.length; i++)
1083 // awidth = (float[]) typeWidth.get(data[i][0]);
1086 width[i] = fr.getOrder(data[i][TYPE_COLUMN].toString());
1089 width[i] = fr.setOrder(data[i][TYPE_COLUMN].toString(),
1095 width[i] /= max; // normalize
1096 fr.setOrder(data[i][TYPE_COLUMN].toString(), width[i]); // store for later
1100 sort = sort || width[i - 1] > width[i];
1105 jalview.util.QuickSort.sort(width, data);
1106 // update global priority order
1109 updateFeatureRenderer(data, false);
1117 frame.setClosed(true);
1118 } catch (Exception exe)
1124 public void updateFeatureRenderer(Object[][] data)
1126 updateFeatureRenderer(data, true);
1130 * Update the priority order of features; only repaint if this changed the order
1131 * of visible features
1136 private void updateFeatureRenderer(Object[][] data, boolean visibleNew)
1138 FeatureSettingsBean[] rowData = getTableAsBeans(data);
1140 if (fr.setFeaturePriority(rowData, visibleNew))
1142 af.alignPanel.paintAlignment(true, true);
1147 * Converts table data into an array of data beans
1149 private FeatureSettingsBean[] getTableAsBeans(Object[][] data)
1151 FeatureSettingsBean[] rowData = new FeatureSettingsBean[data.length];
1152 for (int i = 0; i < data.length; i++)
1154 String type = (String) data[i][TYPE_COLUMN];
1155 FeatureColourI colour = (FeatureColourI) data[i][COLOUR_COLUMN];
1156 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) data[i][FILTER_COLUMN];
1157 Boolean isShown = (Boolean) data[i][SHOW_COLUMN];
1158 rowData[i] = new FeatureSettingsBean(type, colour, theFilter,
1164 private void jbInit() throws Exception
1166 this.setLayout(new BorderLayout());
1168 JPanel settingsPane = new JPanel();
1169 settingsPane.setLayout(new BorderLayout());
1171 JPanel bigPanel = new JPanel();
1172 bigPanel.setLayout(new BorderLayout());
1174 groupPanel = new JPanel();
1175 bigPanel.add(groupPanel, BorderLayout.NORTH);
1177 JButton invert = new JButton(
1178 MessageManager.getString("label.invert_selection"));
1179 invert.setFont(JvSwingUtils.getLabelFont());
1180 invert.addActionListener(new ActionListener()
1183 public void actionPerformed(ActionEvent e)
1189 JButton optimizeOrder = new JButton(
1190 MessageManager.getString("label.optimise_order"));
1191 optimizeOrder.setFont(JvSwingUtils.getLabelFont());
1192 optimizeOrder.addActionListener(new ActionListener()
1195 public void actionPerformed(ActionEvent e)
1201 JButton sortByScore = new JButton(
1202 MessageManager.getString("label.seq_sort_by_score"));
1203 sortByScore.setFont(JvSwingUtils.getLabelFont());
1204 sortByScore.addActionListener(new ActionListener()
1207 public void actionPerformed(ActionEvent e)
1209 af.avc.sortAlignmentByFeatureScore(null);
1212 JButton sortByDens = new JButton(
1213 MessageManager.getString("label.sequence_sort_by_density"));
1214 sortByDens.setFont(JvSwingUtils.getLabelFont());
1215 sortByDens.addActionListener(new ActionListener()
1218 public void actionPerformed(ActionEvent e)
1220 af.avc.sortAlignmentByFeatureDensity(null);
1224 JButton help = new JButton(MessageManager.getString("action.help"));
1225 help.setFont(JvSwingUtils.getLabelFont());
1226 help.addActionListener(new ActionListener()
1229 public void actionPerformed(ActionEvent e)
1233 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1234 } catch (HelpSetException e1)
1236 e1.printStackTrace();
1240 help.setFont(JvSwingUtils.getLabelFont());
1241 help.setText(MessageManager.getString("action.help"));
1242 help.addActionListener(new ActionListener()
1245 public void actionPerformed(ActionEvent e)
1249 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1250 } catch (HelpSetException e1)
1252 e1.printStackTrace();
1257 JButton cancel = new JButton(MessageManager.getString("action.cancel"));
1258 cancel.setFont(JvSwingUtils.getLabelFont());
1259 cancel.addActionListener(new ActionListener()
1262 public void actionPerformed(ActionEvent e)
1264 fr.setTransparency(originalTransparency);
1265 fr.setFeatureFilters(originalFilters);
1266 updateFeatureRenderer(originalData);
1271 JButton ok = new JButton(MessageManager.getString("action.ok"));
1272 ok.setFont(JvSwingUtils.getLabelFont());
1273 ok.addActionListener(new ActionListener()
1276 public void actionPerformed(ActionEvent e)
1282 JButton loadColours = new JButton(
1283 MessageManager.getString("label.load_colours"));
1284 loadColours.setFont(JvSwingUtils.getLabelFont());
1285 loadColours.setToolTipText(
1286 MessageManager.getString("label.load_colours_tooltip"));
1287 loadColours.addActionListener(new ActionListener()
1290 public void actionPerformed(ActionEvent e)
1296 JButton saveColours = new JButton(
1297 MessageManager.getString("label.save_colours"));
1298 saveColours.setFont(JvSwingUtils.getLabelFont());
1299 saveColours.setToolTipText(
1300 MessageManager.getString("label.save_colours_tooltip"));
1301 saveColours.addActionListener(new ActionListener()
1304 public void actionPerformed(ActionEvent e)
1309 transparency.addChangeListener(new ChangeListener()
1312 public void stateChanged(ChangeEvent evt)
1314 if (!inConstruction)
1316 fr.setTransparency((100 - transparency.getValue()) / 100f);
1317 af.alignPanel.paintAlignment(true, true);
1322 transparency.setMaximum(70);
1323 transparency.setToolTipText(
1324 MessageManager.getString("label.transparency_tip"));
1326 JPanel transPanel = new JPanel(new GridLayout(1, 2));
1327 bigPanel.add(transPanel, BorderLayout.SOUTH);
1329 JPanel transbuttons = new JPanel(new GridLayout(5, 1));
1330 transbuttons.add(optimizeOrder);
1331 transbuttons.add(invert);
1332 transbuttons.add(sortByScore);
1333 transbuttons.add(sortByDens);
1334 transbuttons.add(help);
1335 transPanel.add(transparency);
1336 transPanel.add(transbuttons);
1338 JPanel buttonPanel = new JPanel();
1339 buttonPanel.add(ok);
1340 buttonPanel.add(cancel);
1341 buttonPanel.add(loadColours);
1342 buttonPanel.add(saveColours);
1343 bigPanel.add(scrollPane, BorderLayout.CENTER);
1344 settingsPane.add(bigPanel, BorderLayout.CENTER);
1345 settingsPane.add(buttonPanel, BorderLayout.SOUTH);
1346 this.add(settingsPane);
1349 // ///////////////////////////////////////////////////////////////////////
1350 // http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
1351 // ///////////////////////////////////////////////////////////////////////
1352 class FeatureTableModel extends AbstractTableModel
1354 private String[] columnNames = {
1355 MessageManager.getString("label.feature_type"),
1356 MessageManager.getString("action.colour"),
1357 MessageManager.getString("label.filter"),
1358 MessageManager.getString("label.show") };
1360 private Object[][] data;
1362 FeatureTableModel(Object[][] data)
1367 public Object[][] getData()
1372 public void setData(Object[][] data)
1378 public int getColumnCount()
1380 return columnNames.length;
1383 public Object[] getRow(int row)
1389 public int getRowCount()
1395 public String getColumnName(int col)
1397 return columnNames[col];
1401 public Object getValueAt(int row, int col)
1403 return data[row][col];
1407 * Answers the class of the object in column c of the first row of the table
1410 public Class<?> getColumnClass(int c)
1412 Object v = getValueAt(0, c);
1413 return v == null ? null : v.getClass();
1417 public boolean isCellEditable(int row, int col)
1419 return col == 0 ? false : true;
1423 public void setValueAt(Object value, int row, int col)
1425 data[row][col] = value;
1426 fireTableCellUpdated(row, col);
1427 updateFeatureRenderer(data);
1432 class ColorRenderer extends JLabel implements TableCellRenderer
1434 javax.swing.border.Border unselectedBorder = null;
1436 javax.swing.border.Border selectedBorder = null;
1438 final String baseTT = "Click to edit, right/apple click for menu.";
1440 public ColorRenderer()
1442 setOpaque(true); // MUST do this for background to show up.
1443 setHorizontalTextPosition(SwingConstants.CENTER);
1444 setVerticalTextPosition(SwingConstants.CENTER);
1448 public Component getTableCellRendererComponent(JTable tbl, Object color,
1449 boolean isSelected, boolean hasFocus, int row, int column)
1451 FeatureColourI cellColour = (FeatureColourI) color;
1453 setToolTipText(baseTT);
1454 setBackground(tbl.getBackground());
1455 if (!cellColour.isSimpleColour())
1457 Rectangle cr = tbl.getCellRect(row, column, false);
1458 FeatureSettings.renderGraduatedColor(this, cellColour,
1459 (int) cr.getWidth(), (int) cr.getHeight());
1465 setBackground(cellColour.getColour());
1469 if (selectedBorder == null)
1471 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1472 tbl.getSelectionBackground());
1474 setBorder(selectedBorder);
1478 if (unselectedBorder == null)
1480 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1481 tbl.getBackground());
1483 setBorder(unselectedBorder);
1490 class FilterRenderer extends JLabel implements TableCellRenderer
1492 javax.swing.border.Border unselectedBorder = null;
1494 javax.swing.border.Border selectedBorder = null;
1496 public FilterRenderer()
1498 setOpaque(true); // MUST do this for background to show up.
1499 setHorizontalTextPosition(SwingConstants.CENTER);
1500 setVerticalTextPosition(SwingConstants.CENTER);
1504 public Component getTableCellRendererComponent(JTable tbl,
1505 Object filter, boolean isSelected, boolean hasFocus, int row,
1508 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) filter;
1510 String asText = theFilter.toString();
1511 setBackground(tbl.getBackground());
1512 this.setText(asText);
1517 if (selectedBorder == null)
1519 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1520 tbl.getSelectionBackground());
1522 setBorder(selectedBorder);
1526 if (unselectedBorder == null)
1528 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1529 tbl.getBackground());
1531 setBorder(unselectedBorder);
1539 * update comp using rendering settings from gcol
1544 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol)
1546 int w = comp.getWidth(), h = comp.getHeight();
1549 w = (int) comp.getPreferredSize().getWidth();
1550 h = (int) comp.getPreferredSize().getHeight();
1557 renderGraduatedColor(comp, gcol, w, h);
1560 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol,
1563 boolean thr = false;
1564 StringBuilder tt = new StringBuilder();
1565 StringBuilder tx = new StringBuilder();
1567 if (gcol.isColourByAttribute())
1569 tx.append(String.join(":", gcol.getAttributeName()));
1571 else if (!gcol.isColourByLabel())
1573 tx.append(MessageManager.getString("label.score"));
1576 if (gcol.isAboveThreshold())
1580 tt.append("Thresholded (Above ").append(gcol.getThreshold())
1583 if (gcol.isBelowThreshold())
1587 tt.append("Thresholded (Below ").append(gcol.getThreshold())
1590 if (gcol.isColourByLabel())
1592 tt.append("Coloured by label text. ").append(tt);
1597 if (!gcol.isColourByAttribute())
1605 Color newColor = gcol.getMaxColour();
1606 comp.setBackground(newColor);
1607 // System.err.println("Width is " + w / 2);
1608 Icon ficon = new FeatureIcon(gcol, comp.getBackground(), w, h, thr);
1609 comp.setIcon(ficon);
1610 // tt+="RGB value: Max (" + newColor.getRed() + ", "
1611 // + newColor.getGreen() + ", " + newColor.getBlue()
1612 // + ")\nMin (" + minCol.getRed() + ", " + minCol.getGreen()
1613 // + ", " + minCol.getBlue() + ")");
1615 comp.setHorizontalAlignment(SwingConstants.CENTER);
1616 comp.setText(tx.toString());
1617 if (tt.length() > 0)
1619 if (comp.getToolTipText() == null)
1621 comp.setToolTipText(tt.toString());
1625 comp.setToolTipText(
1626 tt.append(" ").append(comp.getToolTipText()).toString());
1631 class ColorEditor extends AbstractCellEditor
1632 implements TableCellEditor, ActionListener
1636 FeatureColourI currentColor;
1638 FeatureTypeSettings chooser;
1644 JColorChooser colorChooser;
1648 protected static final String EDIT = "edit";
1650 int rowSelected = 0;
1652 public ColorEditor(FeatureSettings me)
1655 // Set up the editor (from the table's point of view),
1656 // which is a button.
1657 // This button brings up the color chooser dialog,
1658 // which is the editor from the user's point of view.
1659 button = new JButton();
1660 button.setActionCommand(EDIT);
1661 button.addActionListener(this);
1662 button.setBorderPainted(false);
1663 // Set up the dialog that the button brings up.
1664 colorChooser = new JColorChooser();
1665 dialog = JColorChooser.createDialog(button,
1666 MessageManager.getString("label.select_colour"), true, // modal
1667 colorChooser, this, // OK button handler
1668 null); // no CANCEL button handler
1672 * Handles events from the editor button and from the dialog's OK button.
1675 public void actionPerformed(ActionEvent e)
1677 // todo test e.getSource() instead here
1678 if (EDIT.equals(e.getActionCommand()))
1680 // The user has clicked the cell, so
1681 // bring up the dialog.
1682 if (currentColor.isSimpleColour())
1684 // bring up simple color chooser
1685 button.setBackground(currentColor.getColour());
1686 colorChooser.setColor(currentColor.getColour());
1687 dialog.setVisible(true);
1691 // bring up graduated chooser.
1692 chooser = new FeatureTypeSettings(me.fr, type);
1693 chooser.setRequestFocusEnabled(true);
1694 chooser.requestFocus();
1695 chooser.addActionListener(this);
1696 chooser.showTab(true);
1698 // Make the renderer reappear.
1699 fireEditingStopped();
1704 if (currentColor.isSimpleColour())
1707 * read off colour picked in colour chooser after OK pressed
1709 currentColor = new FeatureColour(colorChooser.getColor());
1710 me.table.setValueAt(currentColor, rowSelected, COLOUR_COLUMN);
1715 * after OK in variable colour dialog, any changes to colour
1716 * (or filters!) are already set in FeatureRenderer, so just
1717 * update table data without triggering updateFeatureRenderer
1719 currentColor = fr.getFeatureColours().get(type);
1720 FeatureMatcherSetI currentFilter = me.fr.getFeatureFilter(type);
1721 if (currentFilter == null)
1723 currentFilter = new FeatureMatcherSet();
1725 Object[] data = ((FeatureTableModel) table.getModel())
1726 .getData()[rowSelected];
1727 data[COLOUR_COLUMN] = currentColor;
1728 data[FILTER_COLUMN] = currentFilter;
1730 fireEditingStopped();
1731 me.table.validate();
1735 // Implement the one CellEditor method that AbstractCellEditor doesn't.
1737 public Object getCellEditorValue()
1739 return currentColor;
1742 // Implement the one method defined by TableCellEditor.
1744 public Component getTableCellEditorComponent(JTable theTable, Object value,
1745 boolean isSelected, int row, int column)
1747 currentColor = (FeatureColourI) value;
1748 this.rowSelected = row;
1749 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1750 button.setOpaque(true);
1751 button.setBackground(me.getBackground());
1752 if (!currentColor.isSimpleColour())
1754 JLabel btn = new JLabel();
1755 btn.setSize(button.getSize());
1756 FeatureSettings.renderGraduatedColor(btn, currentColor);
1757 button.setBackground(btn.getBackground());
1758 button.setIcon(btn.getIcon());
1759 button.setText(btn.getText());
1764 button.setIcon(null);
1765 button.setBackground(currentColor.getColour());
1772 * The cell editor for the Filter column. It displays the text of any filters
1773 * for the feature type in that row (in full as a tooltip, possible abbreviated
1774 * as display text). On click in the cell, opens the Feature Display Settings
1775 * dialog at the Filters tab.
1777 class FilterEditor extends AbstractCellEditor
1778 implements TableCellEditor, ActionListener
1782 FeatureMatcherSetI currentFilter;
1790 protected static final String EDIT = "edit";
1792 int rowSelected = 0;
1794 public FilterEditor(FeatureSettings me)
1797 button = new JButton();
1798 button.setActionCommand(EDIT);
1799 button.addActionListener(this);
1800 button.setBorderPainted(false);
1804 * Handles events from the editor button
1807 public void actionPerformed(ActionEvent e)
1809 if (button == e.getSource())
1811 FeatureTypeSettings chooser = new FeatureTypeSettings(me.fr, type);
1812 chooser.addActionListener(this);
1813 chooser.setRequestFocusEnabled(true);
1814 chooser.requestFocus();
1815 if (lastLocation != null)
1817 // todo open at its last position on screen
1818 chooser.setBounds(lastLocation.x, lastLocation.y,
1819 chooser.getWidth(), chooser.getHeight());
1822 chooser.showTab(false);
1823 fireEditingStopped();
1825 else if (e.getSource() instanceof Component)
1829 * after OK in variable colour dialog, any changes to filter
1830 * (or colours!) are already set in FeatureRenderer, so just
1831 * update table data without triggering updateFeatureRenderer
1833 FeatureColourI currentColor = fr.getFeatureColours().get(type);
1834 currentFilter = me.fr.getFeatureFilter(type);
1835 if (currentFilter == null)
1837 currentFilter = new FeatureMatcherSet();
1839 Object[] data = ((FeatureTableModel) table.getModel())
1840 .getData()[rowSelected];
1841 data[COLOUR_COLUMN] = currentColor;
1842 data[FILTER_COLUMN] = currentFilter;
1843 fireEditingStopped();
1844 me.table.validate();
1849 public Object getCellEditorValue()
1851 return currentFilter;
1855 public Component getTableCellEditorComponent(JTable theTable, Object value,
1856 boolean isSelected, int row, int column)
1858 currentFilter = (FeatureMatcherSetI) value;
1859 this.rowSelected = row;
1860 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1861 button.setOpaque(true);
1862 button.setBackground(me.getBackground());
1863 button.setText(currentFilter.toString());
1864 button.setToolTipText(currentFilter.toString());
1865 button.setIcon(null);
1871 class FeatureIcon implements Icon
1873 FeatureColourI gcol;
1877 boolean midspace = false;
1879 int width = 50, height = 20;
1881 int s1, e1; // start and end of midpoint band for thresholded symbol
1883 Color mpcolour = Color.white;
1885 FeatureIcon(FeatureColourI gfc, Color bg, int w, int h, boolean mspace)
1905 public int getIconWidth()
1911 public int getIconHeight()
1917 public void paintIcon(Component c, Graphics g, int x, int y)
1920 if (gcol.isColourByLabel())
1923 g.fillRect(0, 0, width, height);
1924 // need an icon here.
1925 g.setColor(gcol.getMaxColour());
1927 g.setFont(new Font("Verdana", Font.PLAIN, 9));
1929 // g.setFont(g.getFont().deriveFont(
1930 // AffineTransform.getScaleInstance(
1931 // width/g.getFontMetrics().stringWidth("Label"),
1932 // height/g.getFontMetrics().getHeight())));
1934 g.drawString(MessageManager.getString("label.label"), 0, 0);
1939 Color minCol = gcol.getMinColour();
1941 g.fillRect(0, 0, s1, height);
1944 g.setColor(Color.white);
1945 g.fillRect(s1, 0, e1 - s1, height);
1947 g.setColor(gcol.getMaxColour());
1948 g.fillRect(0, e1, width - e1, height);