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;
48 import java.awt.Graphics;
49 import java.awt.GridLayout;
50 import java.awt.Point;
51 import java.awt.Rectangle;
52 import java.awt.event.ActionEvent;
53 import java.awt.event.ActionListener;
54 import java.awt.event.ItemEvent;
55 import java.awt.event.ItemListener;
56 import java.awt.event.MouseAdapter;
57 import java.awt.event.MouseEvent;
58 import java.awt.event.MouseMotionAdapter;
59 import java.beans.PropertyChangeEvent;
60 import java.beans.PropertyChangeListener;
62 import java.io.FileInputStream;
63 import java.io.FileOutputStream;
64 import java.io.InputStreamReader;
65 import java.io.OutputStreamWriter;
66 import java.io.PrintWriter;
67 import java.util.Arrays;
68 import java.util.Comparator;
69 import java.util.HashMap;
70 import java.util.HashSet;
71 import java.util.Hashtable;
72 import java.util.Iterator;
73 import java.util.List;
77 import javax.help.HelpSetException;
78 import javax.swing.AbstractCellEditor;
79 import javax.swing.BorderFactory;
80 import javax.swing.Icon;
81 import javax.swing.JButton;
82 import javax.swing.JCheckBox;
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();
154 JCheckBox showComplement;
157 * when true, constructor is still executing - so ignore UI events
159 protected volatile boolean inConstruction = true;
161 int selectedRow = -1;
163 JButton fetchDAS = new JButton();
165 JButton saveDAS = new JButton();
167 JButton cancelDAS = new JButton();
169 boolean resettingTable = false;
172 * true when Feature Settings are updating from feature renderer
174 private boolean handlingUpdate = false;
177 * holds {featureCount, totalExtent} for each feature type
179 Map<String, float[]> typeWidth = null;
186 public FeatureSettings(AlignFrame alignFrame)
188 this.af = alignFrame;
189 fr = af.getFeatureRenderer();
191 // save transparency for restore on Cancel
192 originalTransparency = fr.getTransparency();
193 int originalTransparencyAsPercent = (int) (originalTransparency * 100);
194 transparency.setMaximum(100 - originalTransparencyAsPercent);
196 originalFilters = new HashMap<>(fr.getFeatureFilters()); // shallow copy
201 } catch (Exception ex)
203 ex.printStackTrace();
209 public String getToolTipText(MouseEvent e)
212 int column = table.columnAtPoint(e.getPoint());
216 tip = JvSwingUtils.wrapTooltip(true, MessageManager
217 .getString("label.feature_settings_click_drag"));
220 int row = table.rowAtPoint(e.getPoint());
221 FeatureMatcherSet o = (FeatureMatcherSet) table.getValueAt(row,
225 .getString("label.configure_feature_tooltip")
234 table.getTableHeader().setFont(new Font("Verdana", Font.PLAIN, 12));
235 table.setFont(new Font("Verdana", Font.PLAIN, 12));
237 // table.setDefaultRenderer(Color.class, new ColorRenderer());
238 // table.setDefaultEditor(Color.class, new ColorEditor(this));
240 table.setDefaultEditor(FeatureColour.class, new ColorEditor(this));
241 table.setDefaultRenderer(FeatureColour.class, new ColorRenderer());
243 table.setDefaultEditor(FeatureMatcherSet.class, new FilterEditor(this));
244 table.setDefaultRenderer(FeatureMatcherSet.class, new FilterRenderer());
246 TableColumn colourColumn = new TableColumn(COLOUR_COLUMN, 75,
247 new ColorRenderer(), new ColorEditor(this));
248 table.addColumn(colourColumn);
250 TableColumn filterColumn = new TableColumn(FILTER_COLUMN, 75,
251 new FilterRenderer(), new FilterEditor(this));
252 table.addColumn(filterColumn);
254 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
256 table.addMouseListener(new MouseAdapter()
259 public void mousePressed(MouseEvent evt)
261 selectedRow = table.rowAtPoint(evt.getPoint());
262 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
263 if (evt.isPopupTrigger())
265 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
266 popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
269 else if (evt.getClickCount() == 2)
271 boolean invertSelection = evt.isAltDown();
272 boolean toggleSelection = Platform.isControlDown(evt);
273 boolean extendSelection = evt.isShiftDown();
274 fr.ap.alignFrame.avc.markColumnsContainingFeatures(
275 invertSelection, extendSelection, toggleSelection, type);
279 // isPopupTrigger fires on mouseReleased on Windows
281 public void mouseReleased(MouseEvent evt)
283 selectedRow = table.rowAtPoint(evt.getPoint());
284 if (evt.isPopupTrigger())
286 String type = (String) table.getValueAt(selectedRow, TYPE_COLUMN);
287 Object colour = table.getValueAt(selectedRow, COLOUR_COLUMN);
288 popupSort(selectedRow, type, colour, fr.getMinMax(), evt.getX(),
294 table.addMouseMotionListener(new MouseMotionAdapter()
297 public void mouseDragged(MouseEvent evt)
299 int newRow = table.rowAtPoint(evt.getPoint());
300 if (newRow != selectedRow && selectedRow != -1 && newRow != -1)
303 * reposition 'selectedRow' to 'newRow' (the dragged to location)
304 * this could be more than one row away for a very fast drag action
305 * so just swap it with adjacent rows until we get it there
307 Object[][] data = ((FeatureTableModel) table.getModel())
309 int direction = newRow < selectedRow ? -1 : 1;
310 for (int i = selectedRow; i != newRow; i += direction)
312 Object[] temp = data[i];
313 data[i] = data[i + direction];
314 data[i + direction] = temp;
316 updateFeatureRenderer(data);
318 selectedRow = newRow;
322 // table.setToolTipText(JvSwingUtils.wrapTooltip(true,
323 // MessageManager.getString("label.feature_settings_click_drag")));
324 scrollPane.setViewportView(table);
326 if (af.getViewport().isShowSequenceFeatures() || !fr.hasRenderOrder())
328 fr.findAllFeatures(true); // display everything!
331 discoverAllFeatureData();
332 final PropertyChangeListener change;
333 final FeatureSettings fs = this;
334 fr.addPropertyChangeListener(change = new PropertyChangeListener()
337 public void propertyChange(PropertyChangeEvent evt)
339 if (!fs.resettingTable && !fs.handlingUpdate)
341 fs.handlingUpdate = true;
343 // new groups may be added with new sequence feature types only
344 fs.handlingUpdate = false;
350 frame = new JInternalFrame();
351 frame.setContentPane(this);
352 if (Platform.isAMac())
354 Desktop.addInternalFrame(frame,
355 MessageManager.getString("label.sequence_feature_settings"),
360 Desktop.addInternalFrame(frame,
361 MessageManager.getString("label.sequence_feature_settings"),
364 frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
366 frame.addInternalFrameListener(
367 new javax.swing.event.InternalFrameAdapter()
370 public void internalFrameClosed(
371 javax.swing.event.InternalFrameEvent evt)
373 fr.removePropertyChangeListener(change);
376 frame.setLayer(JLayeredPane.PALETTE_LAYER);
377 inConstruction = false;
380 protected void popupSort(final int rowSelected, final String type,
381 final Object typeCol, final Map<String, float[][]> minmax, int x,
384 final FeatureColourI featureColour = (FeatureColourI) typeCol;
386 JPopupMenu men = new JPopupMenu(MessageManager
387 .formatMessage("label.settings_for_param", new String[]
389 JMenuItem scr = new JMenuItem(
390 MessageManager.getString("label.sort_by_score"));
392 final FeatureSettings me = this;
393 scr.addActionListener(new ActionListener()
397 public void actionPerformed(ActionEvent e)
400 .sortAlignmentByFeatureScore(Arrays.asList(new String[]
405 JMenuItem dens = new JMenuItem(
406 MessageManager.getString("label.sort_by_density"));
407 dens.addActionListener(new ActionListener()
411 public void actionPerformed(ActionEvent e)
414 .sortAlignmentByFeatureDensity(Arrays.asList(new String[]
421 JMenuItem selCols = new JMenuItem(
422 MessageManager.getString("label.select_columns_containing"));
423 selCols.addActionListener(new ActionListener()
426 public void actionPerformed(ActionEvent arg0)
428 fr.ap.alignFrame.avc.markColumnsContainingFeatures(false, false,
432 JMenuItem clearCols = new JMenuItem(MessageManager
433 .getString("label.select_columns_not_containing"));
434 clearCols.addActionListener(new ActionListener()
437 public void actionPerformed(ActionEvent arg0)
439 fr.ap.alignFrame.avc.markColumnsContainingFeatures(true, false,
443 JMenuItem hideCols = new JMenuItem(
444 MessageManager.getString("label.hide_columns_containing"));
445 hideCols.addActionListener(new ActionListener()
448 public void actionPerformed(ActionEvent arg0)
450 fr.ap.alignFrame.hideFeatureColumns(type, true);
453 JMenuItem hideOtherCols = new JMenuItem(
454 MessageManager.getString("label.hide_columns_not_containing"));
455 hideOtherCols.addActionListener(new ActionListener()
458 public void actionPerformed(ActionEvent arg0)
460 fr.ap.alignFrame.hideFeatureColumns(type, false);
466 men.add(hideOtherCols);
467 men.show(table, x, y);
471 synchronized public void discoverAllFeatureData()
473 Set<String> allGroups = new HashSet<>();
474 AlignmentI alignment = af.getViewport().getAlignment();
476 for (int i = 0; i < alignment.getHeight(); i++)
478 SequenceI seq = alignment.getSequenceAt(i);
479 for (String group : seq.getFeatures().getFeatureGroups(true))
481 if (group != null && !allGroups.contains(group))
483 allGroups.add(group);
484 checkGroupState(group);
495 * Synchronise gui group list and check visibility of group
498 * @return true if group is visible
500 private boolean checkGroupState(String group)
502 boolean visible = fr.checkGroupVisibility(group, true);
504 for (int g = 0; g < groupPanel.getComponentCount(); g++)
506 if (((JCheckBox) groupPanel.getComponent(g)).getText().equals(group))
508 ((JCheckBox) groupPanel.getComponent(g)).setSelected(visible);
513 final String grp = group;
514 final JCheckBox check = new JCheckBox(group, visible);
515 check.setFont(new Font("Serif", Font.BOLD, 12));
516 check.setToolTipText(group);
517 check.addItemListener(new ItemListener()
520 public void itemStateChanged(ItemEvent evt)
522 fr.setGroupVisibility(check.getText(), check.isSelected());
523 resetTable(new String[] { grp });
527 groupPanel.add(check);
531 synchronized void resetTable(String[] groupChanged)
537 resettingTable = true;
538 typeWidth = new Hashtable<>();
539 // TODO: change avWidth calculation to 'per-sequence' average and use long
542 Set<String> displayableTypes = new HashSet<>();
543 Set<String> foundGroups = new HashSet<>();
546 * determine which feature types may be visible depending on
547 * which groups are selected, and recompute average width data
549 for (int i = 0; i < af.getViewport().getAlignment().getHeight(); i++)
552 SequenceI seq = af.getViewport().getAlignment().getSequenceAt(i);
555 * get the sequence's groups for positional features
556 * and keep track of which groups are visible
558 Set<String> groups = seq.getFeatures().getFeatureGroups(true);
559 Set<String> visibleGroups = new HashSet<>();
560 for (String group : groups)
562 if (group == null || checkGroupState(group))
564 visibleGroups.add(group);
567 foundGroups.addAll(groups);
570 * get distinct feature types for visible groups
571 * record distinct visible types, and their count and total length
573 Set<String> types = seq.getFeatures().getFeatureTypesForGroups(true,
574 visibleGroups.toArray(new String[visibleGroups.size()]));
575 for (String type : types)
577 displayableTypes.add(type);
578 float[] avWidth = typeWidth.get(type);
581 avWidth = new float[2];
582 typeWidth.put(type, avWidth);
584 // todo this could include features with a non-visible group
585 // - do we greatly care?
586 // todo should we include non-displayable features here, and only
587 // update when features are added?
588 avWidth[0] += seq.getFeatures().getFeatureCount(true, type);
589 avWidth[1] += seq.getFeatures().getTotalFeatureLength(type);
593 Object[][] data = new Object[displayableTypes.size()][COLUMN_COUNT];
596 if (fr.hasRenderOrder())
600 fr.findAllFeatures(groupChanged != null); // prod to update
601 // colourschemes. but don't
603 // First add the checks in the previous render order,
604 // in case the window has been closed and reopened
606 List<String> frl = fr.getRenderOrder();
607 for (int ro = frl.size() - 1; ro > -1; ro--)
609 String type = frl.get(ro);
611 if (!displayableTypes.contains(type))
616 data[dataIndex][TYPE_COLUMN] = type;
617 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
618 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
619 data[dataIndex][FILTER_COLUMN] = featureFilter == null
620 ? new FeatureMatcherSet()
622 data[dataIndex][SHOW_COLUMN] = new Boolean(
623 af.getViewport().getFeaturesDisplayed().isVisible(type));
625 displayableTypes.remove(type);
630 * process any extra features belonging only to
631 * a group which was just selected
633 while (!displayableTypes.isEmpty())
635 String type = displayableTypes.iterator().next();
636 data[dataIndex][TYPE_COLUMN] = type;
638 data[dataIndex][COLOUR_COLUMN] = fr.getFeatureStyle(type);
639 if (data[dataIndex][COLOUR_COLUMN] == null)
641 // "Colour has been updated in another view!!"
642 fr.clearRenderOrder();
645 FeatureMatcherSetI featureFilter = fr.getFeatureFilter(type);
646 data[dataIndex][FILTER_COLUMN] = featureFilter == null
647 ? new FeatureMatcherSet()
649 data[dataIndex][SHOW_COLUMN] = new Boolean(true);
651 displayableTypes.remove(type);
654 if (originalData == null)
656 originalData = new Object[data.length][COLUMN_COUNT];
657 for (int i = 0; i < data.length; i++)
659 System.arraycopy(data[i], 0, originalData[i], 0, COLUMN_COUNT);
664 updateOriginalData(data);
667 table.setModel(new FeatureTableModel(data));
668 table.getColumnModel().getColumn(0).setPreferredWidth(200);
670 groupPanel.setLayout(
671 new GridLayout(fr.getFeatureGroupsSize() / 4 + 1, 4));
672 pruneGroups(foundGroups);
673 groupPanel.validate();
675 updateFeatureRenderer(data, groupChanged != null);
676 resettingTable = false;
680 * Updates 'originalData' (used for restore on Cancel) if we detect that changes
681 * have been made outwith this dialog
683 * <li>a new feature type added (and made visible)</li>
684 * <li>a feature colour changed (in the Amend Features dialog)</li>
689 protected void updateOriginalData(Object[][] foundData)
691 // todo LinkedHashMap instead of Object[][] would be nice
693 Object[][] currentData = ((FeatureTableModel) table.getModel())
695 for (Object[] row : foundData)
697 String type = (String) row[TYPE_COLUMN];
698 boolean found = false;
699 for (Object[] current : currentData)
701 if (type.equals(current[TYPE_COLUMN]))
705 * currently dependent on object equality here;
706 * really need an equals method on FeatureColour
708 if (!row[COLOUR_COLUMN].equals(current[COLOUR_COLUMN]))
711 * feature colour has changed externally - update originalData
713 for (Object[] original : originalData)
715 if (type.equals(original[TYPE_COLUMN]))
717 original[COLOUR_COLUMN] = row[COLOUR_COLUMN];
728 * new feature detected - add to original data (on top)
730 Object[][] newData = new Object[originalData.length
732 for (int i = 0; i < originalData.length; i++)
734 System.arraycopy(originalData[i], 0, newData[i + 1], 0,
738 originalData = newData;
744 * Remove from the groups panel any checkboxes for groups that are not in the
745 * foundGroups set. This enables removing a group from the display when the last
746 * feature in that group is deleted.
750 protected void pruneGroups(Set<String> foundGroups)
752 for (int g = 0; g < groupPanel.getComponentCount(); g++)
754 JCheckBox checkbox = (JCheckBox) groupPanel.getComponent(g);
755 if (!foundGroups.contains(checkbox.getText()))
757 groupPanel.remove(checkbox);
763 * reorder data based on the featureRenderers global priority list.
767 private void ensureOrder(Object[][] data)
769 boolean sort = false;
770 float[] order = new float[data.length];
771 for (int i = 0; i < order.length; i++)
773 order[i] = fr.getOrder(data[i][0].toString());
776 order[i] = fr.setOrder(data[i][0].toString(), i / order.length);
780 sort = sort || order[i - 1] > order[i];
785 jalview.util.QuickSort.sort(order, data);
790 * Offers a file chooser dialog, and then loads the feature colours and
791 * filters from file in XML format and unmarshals to Jalview feature settings
795 JalviewFileChooser chooser = new JalviewFileChooser("fc",
796 SEQUENCE_FEATURE_COLOURS);
797 chooser.setFileView(new JalviewFileView());
798 chooser.setDialogTitle(
799 MessageManager.getString("label.load_feature_colours"));
800 chooser.setToolTipText(MessageManager.getString("action.load"));
802 int value = chooser.showOpenDialog(this);
804 if (value == JalviewFileChooser.APPROVE_OPTION)
806 File file = chooser.getSelectedFile();
812 * Loads feature colours and filters from XML stored in the given file
820 InputStreamReader in = new InputStreamReader(
821 new FileInputStream(file), "UTF-8");
823 JAXBContext jc = JAXBContext
824 .newInstance("jalview.xml.binding.jalview");
825 javax.xml.bind.Unmarshaller um = jc.createUnmarshaller();
826 XMLStreamReader streamReader = XMLInputFactory.newInstance()
827 .createXMLStreamReader(in);
828 JAXBElement<JalviewUserColours> jbe = um.unmarshal(streamReader,
829 JalviewUserColours.class);
830 JalviewUserColours jucs = jbe.getValue();
832 // JalviewUserColours jucs = JalviewUserColours.unmarshal(in);
835 * load feature colours
837 for (int i = jucs.getColour().size() - 1; i >= 0; i--)
839 Colour newcol = jucs.getColour().get(i);
840 FeatureColourI colour = jalview.project.Jalview2XML
841 .parseColour(newcol);
842 fr.setColour(newcol.getName(), colour);
843 fr.setOrder(newcol.getName(), i / (float) jucs.getColour().size());
847 * load feature filters; loaded filters will replace any that are
848 * currently defined, other defined filters are left unchanged
850 for (int i = 0; i < jucs.getFilter().size(); i++)
852 Filter filterModel = jucs.getFilter().get(i);
853 String featureType = filterModel.getFeatureType();
854 FeatureMatcherSetI filter = jalview.project.Jalview2XML
855 .parseFilter(featureType, filterModel.getMatcherSet());
856 if (!filter.isEmpty())
858 fr.setFeatureFilter(featureType, filter);
863 * update feature settings table
868 Object[][] data = ((FeatureTableModel) table.getModel())
871 updateFeatureRenderer(data, false);
874 } catch (Exception ex)
876 System.out.println("Error loading User Colour File\n" + ex);
881 * Offers a file chooser dialog, and then saves the current feature colours
882 * and any filters to the selected file in XML format
886 JalviewFileChooser chooser = new JalviewFileChooser("fc",
887 SEQUENCE_FEATURE_COLOURS);
888 chooser.setFileView(new JalviewFileView());
889 chooser.setDialogTitle(
890 MessageManager.getString("label.save_feature_colours"));
891 chooser.setToolTipText(MessageManager.getString("action.save"));
893 int value = chooser.showSaveDialog(this);
895 if (value == JalviewFileChooser.APPROVE_OPTION)
897 save(chooser.getSelectedFile());
902 * Saves feature colours and filters to the given file
908 JalviewUserColours ucs = new JalviewUserColours();
909 ucs.setSchemeName("Sequence Features");
912 PrintWriter out = new PrintWriter(new OutputStreamWriter(
913 new FileOutputStream(file), "UTF-8"));
916 * sort feature types by colour order, from 0 (highest)
919 Set<String> fr_colours = fr.getAllFeatureColours();
920 String[] sortedTypes = fr_colours
921 .toArray(new String[fr_colours.size()]);
922 Arrays.sort(sortedTypes, new Comparator<String>()
925 public int compare(String type1, String type2)
927 return Float.compare(fr.getOrder(type1), fr.getOrder(type2));
932 * save feature colours
934 for (String featureType : sortedTypes)
936 FeatureColourI fcol = fr.getFeatureStyle(featureType);
937 Colour col = jalview.project.Jalview2XML.marshalColour(featureType,
939 ucs.getColour().add(col);
943 * save any feature filters
945 for (String featureType : sortedTypes)
947 FeatureMatcherSetI filter = fr.getFeatureFilter(featureType);
948 if (filter != null && !filter.isEmpty())
950 Iterator<FeatureMatcherI> iterator = filter.getMatchers().iterator();
951 FeatureMatcherI firstMatcher = iterator.next();
952 jalview.xml.binding.jalview.FeatureMatcherSet ms = jalview.project.Jalview2XML
953 .marshalFilter(firstMatcher, iterator,
955 Filter filterModel = new Filter();
956 filterModel.setFeatureType(featureType);
957 filterModel.setMatcherSet(ms);
958 ucs.getFilter().add(filterModel);
961 JAXBContext jaxbContext = JAXBContext
962 .newInstance(JalviewUserColours.class);
963 Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
964 jaxbMarshaller.marshal(
965 new ObjectFactory().createJalviewUserColours(ucs), out);
967 // jaxbMarshaller.marshal(object, pout);
968 // marshaller.marshal(object);
973 } catch (Exception ex)
975 ex.printStackTrace();
979 public void invertSelection()
981 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
982 for (int i = 0; i < data.length; i++)
984 data[i][SHOW_COLUMN] = !(Boolean) data[i][SHOW_COLUMN];
986 updateFeatureRenderer(data, true);
990 public void orderByAvWidth()
992 if (table == null || table.getModel() == null)
996 Object[][] data = ((FeatureTableModel) table.getModel()).getData();
997 float[] width = new float[data.length];
1001 for (int i = 0; i < data.length; i++)
1003 awidth = typeWidth.get(data[i][TYPE_COLUMN]);
1006 width[i] = awidth[1] / awidth[0];// *awidth[0]*awidth[2]; - better
1007 // weight - but have to make per
1008 // sequence, too (awidth[2])
1009 // if (width[i]==1) // hack to distinguish single width sequences.
1020 boolean sort = false;
1021 for (int i = 0; i < width.length; i++)
1023 // awidth = (float[]) typeWidth.get(data[i][0]);
1026 width[i] = fr.getOrder(data[i][TYPE_COLUMN].toString());
1029 width[i] = fr.setOrder(data[i][TYPE_COLUMN].toString(),
1035 width[i] /= max; // normalize
1036 fr.setOrder(data[i][TYPE_COLUMN].toString(), width[i]); // store for later
1040 sort = sort || width[i - 1] > width[i];
1045 jalview.util.QuickSort.sort(width, data);
1046 // update global priority order
1049 updateFeatureRenderer(data, false);
1057 frame.setClosed(true);
1058 } catch (Exception exe)
1064 public void updateFeatureRenderer(Object[][] data)
1066 updateFeatureRenderer(data, true);
1070 * Update the priority order of features; only repaint if this changed the order
1071 * of visible features
1076 private void updateFeatureRenderer(Object[][] data, boolean visibleNew)
1078 FeatureSettingsBean[] rowData = getTableAsBeans(data);
1080 if (fr.setFeaturePriority(rowData, visibleNew))
1087 * Converts table data into an array of data beans
1089 private FeatureSettingsBean[] getTableAsBeans(Object[][] data)
1091 FeatureSettingsBean[] rowData = new FeatureSettingsBean[data.length];
1092 for (int i = 0; i < data.length; i++)
1094 String type = (String) data[i][TYPE_COLUMN];
1095 FeatureColourI colour = (FeatureColourI) data[i][COLOUR_COLUMN];
1096 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) data[i][FILTER_COLUMN];
1097 Boolean isShown = (Boolean) data[i][SHOW_COLUMN];
1098 rowData[i] = new FeatureSettingsBean(type, colour, theFilter,
1104 private void jbInit() throws Exception
1106 this.setLayout(new BorderLayout());
1108 JPanel settingsPane = new JPanel();
1109 settingsPane.setLayout(new BorderLayout());
1111 JPanel bigPanel = new JPanel();
1112 bigPanel.setLayout(new BorderLayout());
1114 groupPanel = new JPanel();
1115 bigPanel.add(groupPanel, BorderLayout.NORTH);
1117 JButton invert = new JButton(
1118 MessageManager.getString("label.invert_selection"));
1119 invert.setFont(JvSwingUtils.getLabelFont());
1120 invert.addActionListener(new ActionListener()
1123 public void actionPerformed(ActionEvent e)
1129 JButton optimizeOrder = new JButton(
1130 MessageManager.getString("label.optimise_order"));
1131 optimizeOrder.setFont(JvSwingUtils.getLabelFont());
1132 optimizeOrder.addActionListener(new ActionListener()
1135 public void actionPerformed(ActionEvent e)
1141 JButton sortByScore = new JButton(
1142 MessageManager.getString("label.seq_sort_by_score"));
1143 sortByScore.setFont(JvSwingUtils.getLabelFont());
1144 sortByScore.addActionListener(new ActionListener()
1147 public void actionPerformed(ActionEvent e)
1149 af.avc.sortAlignmentByFeatureScore(null);
1152 JButton sortByDens = new JButton(
1153 MessageManager.getString("label.sequence_sort_by_density"));
1154 sortByDens.setFont(JvSwingUtils.getLabelFont());
1155 sortByDens.addActionListener(new ActionListener()
1158 public void actionPerformed(ActionEvent e)
1160 af.avc.sortAlignmentByFeatureDensity(null);
1164 JButton help = new JButton(MessageManager.getString("action.help"));
1165 help.setFont(JvSwingUtils.getLabelFont());
1166 help.addActionListener(new ActionListener()
1169 public void actionPerformed(ActionEvent e)
1173 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1174 } catch (HelpSetException e1)
1176 e1.printStackTrace();
1180 help.setFont(JvSwingUtils.getLabelFont());
1181 help.setText(MessageManager.getString("action.help"));
1182 help.addActionListener(new ActionListener()
1185 public void actionPerformed(ActionEvent e)
1189 Help.showHelpWindow(HelpId.SequenceFeatureSettings);
1190 } catch (HelpSetException e1)
1192 e1.printStackTrace();
1197 JButton cancel = new JButton(MessageManager.getString("action.cancel"));
1198 cancel.setFont(JvSwingUtils.getLabelFont());
1199 cancel.addActionListener(new ActionListener()
1202 public void actionPerformed(ActionEvent e)
1204 fr.setTransparency(originalTransparency);
1205 fr.setFeatureFilters(originalFilters);
1206 updateFeatureRenderer(originalData);
1211 JButton ok = new JButton(MessageManager.getString("action.ok"));
1212 ok.setFont(JvSwingUtils.getLabelFont());
1213 ok.addActionListener(new ActionListener()
1216 public void actionPerformed(ActionEvent e)
1222 JButton loadColours = new JButton(
1223 MessageManager.getString("label.load_colours"));
1224 loadColours.setFont(JvSwingUtils.getLabelFont());
1225 loadColours.setToolTipText(
1226 MessageManager.getString("label.load_colours_tooltip"));
1227 loadColours.addActionListener(new ActionListener()
1230 public void actionPerformed(ActionEvent e)
1236 JButton saveColours = new JButton(
1237 MessageManager.getString("label.save_colours"));
1238 saveColours.setFont(JvSwingUtils.getLabelFont());
1239 saveColours.setToolTipText(
1240 MessageManager.getString("label.save_colours_tooltip"));
1241 saveColours.addActionListener(new ActionListener()
1244 public void actionPerformed(ActionEvent e)
1249 transparency.addChangeListener(new ChangeListener()
1252 public void stateChanged(ChangeEvent evt)
1254 if (!inConstruction)
1256 fr.setTransparency((100 - transparency.getValue()) / 100f);
1262 transparency.setMaximum(70);
1263 transparency.setToolTipText(
1264 MessageManager.getString("label.transparency_tip"));
1266 boolean nucleotide = af.getViewport().getAlignment().isNucleotide();
1267 showComplement = new JCheckBox(
1268 "Show " + (nucleotide ? "protein" : "CDS") + " features");
1269 showComplement.addActionListener(new ActionListener()
1272 public void actionPerformed(ActionEvent e)
1275 .setShowComplementFeatures(showComplement.isSelected());
1280 JPanel transPanel = new JPanel(new GridLayout(1, 2));
1281 bigPanel.add(transPanel, BorderLayout.SOUTH);
1283 JPanel transbuttons = new JPanel(new GridLayout(5, 1));
1284 transbuttons.add(optimizeOrder);
1285 transbuttons.add(invert);
1286 transbuttons.add(sortByScore);
1287 transbuttons.add(sortByDens);
1288 transbuttons.add(help);
1290 boolean hasComplement = af.getViewport().getCodingComplement() != null;
1291 JPanel transPanelLeft = new JPanel(
1292 new GridLayout(hasComplement ? 2 : 1, 1));
1293 transPanelLeft.add(transparency);
1296 transPanelLeft.add(showComplement);
1298 transPanel.add(transPanelLeft);
1299 transPanel.add(transbuttons);
1301 JPanel buttonPanel = new JPanel();
1302 buttonPanel.add(ok);
1303 buttonPanel.add(cancel);
1304 buttonPanel.add(loadColours);
1305 buttonPanel.add(saveColours);
1306 bigPanel.add(scrollPane, BorderLayout.CENTER);
1307 settingsPane.add(bigPanel, BorderLayout.CENTER);
1308 settingsPane.add(buttonPanel, BorderLayout.SOUTH);
1309 this.add(settingsPane);
1313 * Repaints alignment, structure and overview (if shown). If there is a
1314 * complementary view which is showing this view's features, then also
1317 void refreshDisplay()
1319 af.alignPanel.paintAlignment(true, true);
1320 AlignViewportI complement = af.getViewport().getCodingComplement();
1321 if (complement != null && complement.isShowComplementFeatures())
1323 AlignFrame af2 = Desktop.getAlignFrameFor(complement);
1324 af2.alignPanel.paintAlignment(true, true);
1328 // ///////////////////////////////////////////////////////////////////////
1329 // http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
1330 // ///////////////////////////////////////////////////////////////////////
1331 class FeatureTableModel extends AbstractTableModel
1333 private String[] columnNames = {
1334 MessageManager.getString("label.feature_type"),
1335 MessageManager.getString("action.colour"),
1336 MessageManager.getString("label.configuration"),
1337 MessageManager.getString("label.show") };
1339 private Object[][] data;
1341 FeatureTableModel(Object[][] data)
1346 public Object[][] getData()
1351 public void setData(Object[][] data)
1357 public int getColumnCount()
1359 return columnNames.length;
1362 public Object[] getRow(int row)
1368 public int getRowCount()
1374 public String getColumnName(int col)
1376 return columnNames[col];
1380 public Object getValueAt(int row, int col)
1382 return data[row][col];
1386 * Answers the class of the object in column c of the first row of the table
1389 public Class<?> getColumnClass(int c)
1391 Object v = getValueAt(0, c);
1392 return v == null ? null : v.getClass();
1396 public boolean isCellEditable(int row, int col)
1398 return col == 0 ? false : true;
1402 public void setValueAt(Object value, int row, int col)
1404 data[row][col] = value;
1405 fireTableCellUpdated(row, col);
1406 updateFeatureRenderer(data);
1411 class ColorRenderer extends JLabel implements TableCellRenderer
1413 javax.swing.border.Border unselectedBorder = null;
1415 javax.swing.border.Border selectedBorder = null;
1417 final String baseTT = "Click to edit, right/apple click for menu.";
1419 public ColorRenderer()
1421 setOpaque(true); // MUST do this for background to show up.
1422 setHorizontalTextPosition(SwingConstants.CENTER);
1423 setVerticalTextPosition(SwingConstants.CENTER);
1427 public Component getTableCellRendererComponent(JTable tbl, Object color,
1428 boolean isSelected, boolean hasFocus, int row, int column)
1430 FeatureColourI cellColour = (FeatureColourI) color;
1432 setToolTipText(baseTT);
1433 setBackground(tbl.getBackground());
1434 if (!cellColour.isSimpleColour())
1436 Rectangle cr = tbl.getCellRect(row, column, false);
1437 FeatureSettings.renderGraduatedColor(this, cellColour,
1438 (int) cr.getWidth(), (int) cr.getHeight());
1444 setBackground(cellColour.getColour());
1448 if (selectedBorder == null)
1450 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1451 tbl.getSelectionBackground());
1453 setBorder(selectedBorder);
1457 if (unselectedBorder == null)
1459 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1460 tbl.getBackground());
1462 setBorder(unselectedBorder);
1469 class FilterRenderer extends JLabel implements TableCellRenderer
1471 javax.swing.border.Border unselectedBorder = null;
1473 javax.swing.border.Border selectedBorder = null;
1475 public FilterRenderer()
1477 setOpaque(true); // MUST do this for background to show up.
1478 setHorizontalTextPosition(SwingConstants.CENTER);
1479 setVerticalTextPosition(SwingConstants.CENTER);
1483 public Component getTableCellRendererComponent(JTable tbl,
1484 Object filter, boolean isSelected, boolean hasFocus, int row,
1487 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) filter;
1489 String asText = theFilter.toString();
1490 setBackground(tbl.getBackground());
1491 this.setText(asText);
1496 if (selectedBorder == null)
1498 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1499 tbl.getSelectionBackground());
1501 setBorder(selectedBorder);
1505 if (unselectedBorder == null)
1507 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1508 tbl.getBackground());
1510 setBorder(unselectedBorder);
1518 * update comp using rendering settings from gcol
1523 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol)
1525 int w = comp.getWidth(), h = comp.getHeight();
1528 w = (int) comp.getPreferredSize().getWidth();
1529 h = (int) comp.getPreferredSize().getHeight();
1536 renderGraduatedColor(comp, gcol, w, h);
1539 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol,
1542 boolean thr = false;
1543 StringBuilder tt = new StringBuilder();
1544 StringBuilder tx = new StringBuilder();
1546 if (gcol.isColourByAttribute())
1548 tx.append(String.join(":", gcol.getAttributeName()));
1550 else if (!gcol.isColourByLabel())
1552 tx.append(MessageManager.getString("label.score"));
1555 if (gcol.isAboveThreshold())
1559 tt.append("Thresholded (Above ").append(gcol.getThreshold())
1562 if (gcol.isBelowThreshold())
1566 tt.append("Thresholded (Below ").append(gcol.getThreshold())
1569 if (gcol.isColourByLabel())
1571 tt.append("Coloured by label text. ").append(tt);
1576 if (!gcol.isColourByAttribute())
1584 Color newColor = gcol.getMaxColour();
1585 comp.setBackground(newColor);
1586 // System.err.println("Width is " + w / 2);
1587 Icon ficon = new FeatureIcon(gcol, comp.getBackground(), w, h, thr);
1588 comp.setIcon(ficon);
1589 // tt+="RGB value: Max (" + newColor.getRed() + ", "
1590 // + newColor.getGreen() + ", " + newColor.getBlue()
1591 // + ")\nMin (" + minCol.getRed() + ", " + minCol.getGreen()
1592 // + ", " + minCol.getBlue() + ")");
1594 comp.setHorizontalAlignment(SwingConstants.CENTER);
1595 comp.setText(tx.toString());
1596 if (tt.length() > 0)
1598 if (comp.getToolTipText() == null)
1600 comp.setToolTipText(tt.toString());
1604 comp.setToolTipText(
1605 tt.append(" ").append(comp.getToolTipText()).toString());
1610 class ColorEditor extends AbstractCellEditor
1611 implements TableCellEditor, ActionListener
1615 FeatureColourI currentColor;
1617 FeatureTypeSettings chooser;
1623 JColorChooser colorChooser;
1627 protected static final String EDIT = "edit";
1629 int rowSelected = 0;
1631 public ColorEditor(FeatureSettings me)
1634 // Set up the editor (from the table's point of view),
1635 // which is a button.
1636 // This button brings up the color chooser dialog,
1637 // which is the editor from the user's point of view.
1638 button = new JButton();
1639 button.setActionCommand(EDIT);
1640 button.addActionListener(this);
1641 button.setBorderPainted(false);
1642 // Set up the dialog that the button brings up.
1643 colorChooser = new JColorChooser();
1644 dialog = JColorChooser.createDialog(button,
1645 MessageManager.getString("label.select_colour"), true, // modal
1646 colorChooser, this, // OK button handler
1647 null); // no CANCEL button handler
1651 * Handles events from the editor button and from the dialog's OK button.
1654 public void actionPerformed(ActionEvent e)
1656 // todo test e.getSource() instead here
1657 if (EDIT.equals(e.getActionCommand()))
1659 // The user has clicked the cell, so
1660 // bring up the dialog.
1661 if (currentColor.isSimpleColour())
1663 // bring up simple color chooser
1664 button.setBackground(currentColor.getColour());
1665 colorChooser.setColor(currentColor.getColour());
1666 dialog.setVisible(true);
1670 // bring up graduated chooser.
1671 chooser = new FeatureTypeSettings(me.fr, type);
1676 chooser.setRequestFocusEnabled(true);
1677 chooser.requestFocus();
1679 chooser.addActionListener(this);
1680 // Make the renderer reappear.
1681 fireEditingStopped();
1686 if (currentColor.isSimpleColour())
1689 * read off colour picked in colour chooser after OK pressed
1691 currentColor = new FeatureColour(colorChooser.getColor());
1692 me.table.setValueAt(currentColor, rowSelected, COLOUR_COLUMN);
1697 * after OK in variable colour dialog, any changes to colour
1698 * (or filters!) are already set in FeatureRenderer, so just
1699 * update table data without triggering updateFeatureRenderer
1701 currentColor = fr.getFeatureColours().get(type);
1702 FeatureMatcherSetI currentFilter = me.fr.getFeatureFilter(type);
1703 if (currentFilter == null)
1705 currentFilter = new FeatureMatcherSet();
1707 Object[] data = ((FeatureTableModel) table.getModel())
1708 .getData()[rowSelected];
1709 data[COLOUR_COLUMN] = currentColor;
1710 data[FILTER_COLUMN] = currentFilter;
1712 fireEditingStopped();
1713 me.table.validate();
1717 // Implement the one CellEditor method that AbstractCellEditor doesn't.
1719 public Object getCellEditorValue()
1721 return currentColor;
1724 // Implement the one method defined by TableCellEditor.
1726 public Component getTableCellEditorComponent(JTable theTable, Object value,
1727 boolean isSelected, int row, int column)
1729 currentColor = (FeatureColourI) value;
1730 this.rowSelected = row;
1731 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1732 button.setOpaque(true);
1733 button.setBackground(me.getBackground());
1734 if (!currentColor.isSimpleColour())
1736 JLabel btn = new JLabel();
1737 btn.setSize(button.getSize());
1738 FeatureSettings.renderGraduatedColor(btn, currentColor);
1739 button.setBackground(btn.getBackground());
1740 button.setIcon(btn.getIcon());
1741 button.setText(btn.getText());
1746 button.setIcon(null);
1747 button.setBackground(currentColor.getColour());
1754 * The cell editor for the Filter column. It displays the text of any filters
1755 * for the feature type in that row (in full as a tooltip, possible abbreviated
1756 * as display text). On click in the cell, opens the Feature Display Settings
1757 * dialog at the Filters tab.
1759 class FilterEditor extends AbstractCellEditor
1760 implements TableCellEditor, ActionListener
1764 FeatureMatcherSetI currentFilter;
1772 protected static final String EDIT = "edit";
1774 int rowSelected = 0;
1776 public FilterEditor(FeatureSettings me)
1779 button = new JButton();
1780 button.setActionCommand(EDIT);
1781 button.addActionListener(this);
1782 button.setBorderPainted(false);
1786 * Handles events from the editor button
1789 public void actionPerformed(ActionEvent e)
1791 if (button == e.getSource())
1793 FeatureTypeSettings chooser = new FeatureTypeSettings(me.fr, type);
1794 chooser.addActionListener(this);
1795 chooser.setRequestFocusEnabled(true);
1796 chooser.requestFocus();
1797 if (lastLocation != null)
1799 // todo open at its last position on screen
1800 chooser.setBounds(lastLocation.x, lastLocation.y,
1801 chooser.getWidth(), chooser.getHeight());
1804 fireEditingStopped();
1806 else if (e.getSource() instanceof Component)
1810 * after OK in variable colour dialog, any changes to filter
1811 * (or colours!) are already set in FeatureRenderer, so just
1812 * update table data without triggering updateFeatureRenderer
1814 FeatureColourI currentColor = fr.getFeatureColours().get(type);
1815 currentFilter = me.fr.getFeatureFilter(type);
1816 if (currentFilter == null)
1818 currentFilter = new FeatureMatcherSet();
1820 Object[] data = ((FeatureTableModel) table.getModel())
1821 .getData()[rowSelected];
1822 data[COLOUR_COLUMN] = currentColor;
1823 data[FILTER_COLUMN] = currentFilter;
1824 fireEditingStopped();
1825 me.table.validate();
1830 public Object getCellEditorValue()
1832 return currentFilter;
1836 public Component getTableCellEditorComponent(JTable theTable, Object value,
1837 boolean isSelected, int row, int column)
1839 currentFilter = (FeatureMatcherSetI) value;
1840 this.rowSelected = row;
1841 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1842 button.setOpaque(true);
1843 button.setBackground(me.getBackground());
1844 button.setText(currentFilter.toString());
1845 button.setToolTipText(currentFilter.toString());
1846 button.setIcon(null);
1852 class FeatureIcon implements Icon
1854 FeatureColourI gcol;
1858 boolean midspace = false;
1860 int width = 50, height = 20;
1862 int s1, e1; // start and end of midpoint band for thresholded symbol
1864 Color mpcolour = Color.white;
1866 FeatureIcon(FeatureColourI gfc, Color bg, int w, int h, boolean mspace)
1886 public int getIconWidth()
1892 public int getIconHeight()
1898 public void paintIcon(Component c, Graphics g, int x, int y)
1901 if (gcol.isColourByLabel())
1904 g.fillRect(0, 0, width, height);
1905 // need an icon here.
1906 g.setColor(gcol.getMaxColour());
1908 g.setFont(new Font("Verdana", Font.PLAIN, 9));
1910 // g.setFont(g.getFont().deriveFont(
1911 // AffineTransform.getScaleInstance(
1912 // width/g.getFontMetrics().stringWidth("Label"),
1913 // height/g.getFontMetrics().getHeight())));
1915 g.drawString(MessageManager.getString("label.label"), 0, 0);
1920 Color minCol = gcol.getMinColour();
1922 g.fillRect(0, 0, s1, height);
1925 g.setColor(Color.white);
1926 g.fillRect(s1, 0, e1 - s1, height);
1928 g.setColor(gcol.getMaxColour());
1929 g.fillRect(0, e1, width - e1, height);