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.setSelected(af.getViewport().isShowComplementFeatures());
1270 showComplement.addActionListener(new ActionListener()
1273 public void actionPerformed(ActionEvent e)
1276 .setShowComplementFeatures(showComplement.isSelected());
1281 JPanel transPanel = new JPanel(new GridLayout(1, 2));
1282 bigPanel.add(transPanel, BorderLayout.SOUTH);
1284 JPanel transbuttons = new JPanel(new GridLayout(5, 1));
1285 transbuttons.add(optimizeOrder);
1286 transbuttons.add(invert);
1287 transbuttons.add(sortByScore);
1288 transbuttons.add(sortByDens);
1289 transbuttons.add(help);
1291 boolean hasComplement = af.getViewport().getCodingComplement() != null;
1292 JPanel transPanelLeft = new JPanel(
1293 new GridLayout(hasComplement ? 2 : 1, 1));
1294 transPanelLeft.add(transparency);
1297 transPanelLeft.add(showComplement);
1299 transPanel.add(transPanelLeft);
1300 transPanel.add(transbuttons);
1302 JPanel buttonPanel = new JPanel();
1303 buttonPanel.add(ok);
1304 buttonPanel.add(cancel);
1305 buttonPanel.add(loadColours);
1306 buttonPanel.add(saveColours);
1307 bigPanel.add(scrollPane, BorderLayout.CENTER);
1308 settingsPane.add(bigPanel, BorderLayout.CENTER);
1309 settingsPane.add(buttonPanel, BorderLayout.SOUTH);
1310 this.add(settingsPane);
1314 * Repaints alignment, structure and overview (if shown). If there is a
1315 * complementary view which is showing this view's features, then also
1318 void refreshDisplay()
1320 af.alignPanel.paintAlignment(true, true);
1321 AlignViewportI complement = af.getViewport().getCodingComplement();
1322 if (complement != null && complement.isShowComplementFeatures())
1324 AlignFrame af2 = Desktop.getAlignFrameFor(complement);
1325 af2.alignPanel.paintAlignment(true, true);
1329 // ///////////////////////////////////////////////////////////////////////
1330 // http://java.sun.com/docs/books/tutorial/uiswing/components/table.html
1331 // ///////////////////////////////////////////////////////////////////////
1332 class FeatureTableModel extends AbstractTableModel
1334 private String[] columnNames = {
1335 MessageManager.getString("label.feature_type"),
1336 MessageManager.getString("action.colour"),
1337 MessageManager.getString("label.configuration"),
1338 MessageManager.getString("label.show") };
1340 private Object[][] data;
1342 FeatureTableModel(Object[][] data)
1347 public Object[][] getData()
1352 public void setData(Object[][] data)
1358 public int getColumnCount()
1360 return columnNames.length;
1363 public Object[] getRow(int row)
1369 public int getRowCount()
1375 public String getColumnName(int col)
1377 return columnNames[col];
1381 public Object getValueAt(int row, int col)
1383 return data[row][col];
1387 * Answers the class of the object in column c of the first row of the table
1390 public Class<?> getColumnClass(int c)
1392 Object v = getValueAt(0, c);
1393 return v == null ? null : v.getClass();
1397 public boolean isCellEditable(int row, int col)
1399 return col == 0 ? false : true;
1403 public void setValueAt(Object value, int row, int col)
1405 data[row][col] = value;
1406 fireTableCellUpdated(row, col);
1407 updateFeatureRenderer(data);
1412 class ColorRenderer extends JLabel implements TableCellRenderer
1414 javax.swing.border.Border unselectedBorder = null;
1416 javax.swing.border.Border selectedBorder = null;
1418 final String baseTT = "Click to edit, right/apple click for menu.";
1420 public ColorRenderer()
1422 setOpaque(true); // MUST do this for background to show up.
1423 setHorizontalTextPosition(SwingConstants.CENTER);
1424 setVerticalTextPosition(SwingConstants.CENTER);
1428 public Component getTableCellRendererComponent(JTable tbl, Object color,
1429 boolean isSelected, boolean hasFocus, int row, int column)
1431 FeatureColourI cellColour = (FeatureColourI) color;
1433 setToolTipText(baseTT);
1434 setBackground(tbl.getBackground());
1435 if (!cellColour.isSimpleColour())
1437 Rectangle cr = tbl.getCellRect(row, column, false);
1438 FeatureSettings.renderGraduatedColor(this, cellColour,
1439 (int) cr.getWidth(), (int) cr.getHeight());
1445 setBackground(cellColour.getColour());
1449 if (selectedBorder == null)
1451 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1452 tbl.getSelectionBackground());
1454 setBorder(selectedBorder);
1458 if (unselectedBorder == null)
1460 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1461 tbl.getBackground());
1463 setBorder(unselectedBorder);
1470 class FilterRenderer extends JLabel implements TableCellRenderer
1472 javax.swing.border.Border unselectedBorder = null;
1474 javax.swing.border.Border selectedBorder = null;
1476 public FilterRenderer()
1478 setOpaque(true); // MUST do this for background to show up.
1479 setHorizontalTextPosition(SwingConstants.CENTER);
1480 setVerticalTextPosition(SwingConstants.CENTER);
1484 public Component getTableCellRendererComponent(JTable tbl,
1485 Object filter, boolean isSelected, boolean hasFocus, int row,
1488 FeatureMatcherSetI theFilter = (FeatureMatcherSetI) filter;
1490 String asText = theFilter.toString();
1491 setBackground(tbl.getBackground());
1492 this.setText(asText);
1497 if (selectedBorder == null)
1499 selectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1500 tbl.getSelectionBackground());
1502 setBorder(selectedBorder);
1506 if (unselectedBorder == null)
1508 unselectedBorder = BorderFactory.createMatteBorder(2, 5, 2, 5,
1509 tbl.getBackground());
1511 setBorder(unselectedBorder);
1519 * update comp using rendering settings from gcol
1524 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol)
1526 int w = comp.getWidth(), h = comp.getHeight();
1529 w = (int) comp.getPreferredSize().getWidth();
1530 h = (int) comp.getPreferredSize().getHeight();
1537 renderGraduatedColor(comp, gcol, w, h);
1540 public static void renderGraduatedColor(JLabel comp, FeatureColourI gcol,
1543 boolean thr = false;
1544 StringBuilder tt = new StringBuilder();
1545 StringBuilder tx = new StringBuilder();
1547 if (gcol.isColourByAttribute())
1549 tx.append(String.join(":", gcol.getAttributeName()));
1551 else if (!gcol.isColourByLabel())
1553 tx.append(MessageManager.getString("label.score"));
1556 if (gcol.isAboveThreshold())
1560 tt.append("Thresholded (Above ").append(gcol.getThreshold())
1563 if (gcol.isBelowThreshold())
1567 tt.append("Thresholded (Below ").append(gcol.getThreshold())
1570 if (gcol.isColourByLabel())
1572 tt.append("Coloured by label text. ").append(tt);
1577 if (!gcol.isColourByAttribute())
1585 Color newColor = gcol.getMaxColour();
1586 comp.setBackground(newColor);
1587 // System.err.println("Width is " + w / 2);
1588 Icon ficon = new FeatureIcon(gcol, comp.getBackground(), w, h, thr);
1589 comp.setIcon(ficon);
1590 // tt+="RGB value: Max (" + newColor.getRed() + ", "
1591 // + newColor.getGreen() + ", " + newColor.getBlue()
1592 // + ")\nMin (" + minCol.getRed() + ", " + minCol.getGreen()
1593 // + ", " + minCol.getBlue() + ")");
1595 comp.setHorizontalAlignment(SwingConstants.CENTER);
1596 comp.setText(tx.toString());
1597 if (tt.length() > 0)
1599 if (comp.getToolTipText() == null)
1601 comp.setToolTipText(tt.toString());
1605 comp.setToolTipText(
1606 tt.append(" ").append(comp.getToolTipText()).toString());
1611 class ColorEditor extends AbstractCellEditor
1612 implements TableCellEditor, ActionListener
1616 FeatureColourI currentColor;
1618 FeatureTypeSettings chooser;
1624 JColorChooser colorChooser;
1628 protected static final String EDIT = "edit";
1630 int rowSelected = 0;
1632 public ColorEditor(FeatureSettings me)
1635 // Set up the editor (from the table's point of view),
1636 // which is a button.
1637 // This button brings up the color chooser dialog,
1638 // which is the editor from the user's point of view.
1639 button = new JButton();
1640 button.setActionCommand(EDIT);
1641 button.addActionListener(this);
1642 button.setBorderPainted(false);
1643 // Set up the dialog that the button brings up.
1644 colorChooser = new JColorChooser();
1645 dialog = JColorChooser.createDialog(button,
1646 MessageManager.getString("label.select_colour"), true, // modal
1647 colorChooser, this, // OK button handler
1648 null); // no CANCEL button handler
1652 * Handles events from the editor button and from the dialog's OK button.
1655 public void actionPerformed(ActionEvent e)
1657 // todo test e.getSource() instead here
1658 if (EDIT.equals(e.getActionCommand()))
1660 // The user has clicked the cell, so
1661 // bring up the dialog.
1662 if (currentColor.isSimpleColour())
1664 // bring up simple color chooser
1665 button.setBackground(currentColor.getColour());
1666 colorChooser.setColor(currentColor.getColour());
1667 dialog.setVisible(true);
1671 // bring up graduated chooser.
1672 chooser = new FeatureTypeSettings(me.fr, type);
1677 chooser.setRequestFocusEnabled(true);
1678 chooser.requestFocus();
1680 chooser.addActionListener(this);
1681 // Make the renderer reappear.
1682 fireEditingStopped();
1687 if (currentColor.isSimpleColour())
1690 * read off colour picked in colour chooser after OK pressed
1692 currentColor = new FeatureColour(colorChooser.getColor());
1693 me.table.setValueAt(currentColor, rowSelected, COLOUR_COLUMN);
1698 * after OK in variable colour dialog, any changes to colour
1699 * (or filters!) are already set in FeatureRenderer, so just
1700 * update table data without triggering updateFeatureRenderer
1702 currentColor = fr.getFeatureColours().get(type);
1703 FeatureMatcherSetI currentFilter = me.fr.getFeatureFilter(type);
1704 if (currentFilter == null)
1706 currentFilter = new FeatureMatcherSet();
1708 Object[] data = ((FeatureTableModel) table.getModel())
1709 .getData()[rowSelected];
1710 data[COLOUR_COLUMN] = currentColor;
1711 data[FILTER_COLUMN] = currentFilter;
1713 fireEditingStopped();
1714 me.table.validate();
1718 // Implement the one CellEditor method that AbstractCellEditor doesn't.
1720 public Object getCellEditorValue()
1722 return currentColor;
1725 // Implement the one method defined by TableCellEditor.
1727 public Component getTableCellEditorComponent(JTable theTable, Object value,
1728 boolean isSelected, int row, int column)
1730 currentColor = (FeatureColourI) value;
1731 this.rowSelected = row;
1732 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1733 button.setOpaque(true);
1734 button.setBackground(me.getBackground());
1735 if (!currentColor.isSimpleColour())
1737 JLabel btn = new JLabel();
1738 btn.setSize(button.getSize());
1739 FeatureSettings.renderGraduatedColor(btn, currentColor);
1740 button.setBackground(btn.getBackground());
1741 button.setIcon(btn.getIcon());
1742 button.setText(btn.getText());
1747 button.setIcon(null);
1748 button.setBackground(currentColor.getColour());
1755 * The cell editor for the Filter column. It displays the text of any filters
1756 * for the feature type in that row (in full as a tooltip, possible abbreviated
1757 * as display text). On click in the cell, opens the Feature Display Settings
1758 * dialog at the Filters tab.
1760 class FilterEditor extends AbstractCellEditor
1761 implements TableCellEditor, ActionListener
1765 FeatureMatcherSetI currentFilter;
1773 protected static final String EDIT = "edit";
1775 int rowSelected = 0;
1777 public FilterEditor(FeatureSettings me)
1780 button = new JButton();
1781 button.setActionCommand(EDIT);
1782 button.addActionListener(this);
1783 button.setBorderPainted(false);
1787 * Handles events from the editor button
1790 public void actionPerformed(ActionEvent e)
1792 if (button == e.getSource())
1794 FeatureTypeSettings chooser = new FeatureTypeSettings(me.fr, type);
1795 chooser.addActionListener(this);
1796 chooser.setRequestFocusEnabled(true);
1797 chooser.requestFocus();
1798 if (lastLocation != null)
1800 // todo open at its last position on screen
1801 chooser.setBounds(lastLocation.x, lastLocation.y,
1802 chooser.getWidth(), chooser.getHeight());
1805 fireEditingStopped();
1807 else if (e.getSource() instanceof Component)
1811 * after OK in variable colour dialog, any changes to filter
1812 * (or colours!) are already set in FeatureRenderer, so just
1813 * update table data without triggering updateFeatureRenderer
1815 FeatureColourI currentColor = fr.getFeatureColours().get(type);
1816 currentFilter = me.fr.getFeatureFilter(type);
1817 if (currentFilter == null)
1819 currentFilter = new FeatureMatcherSet();
1821 Object[] data = ((FeatureTableModel) table.getModel())
1822 .getData()[rowSelected];
1823 data[COLOUR_COLUMN] = currentColor;
1824 data[FILTER_COLUMN] = currentFilter;
1825 fireEditingStopped();
1826 me.table.validate();
1831 public Object getCellEditorValue()
1833 return currentFilter;
1837 public Component getTableCellEditorComponent(JTable theTable, Object value,
1838 boolean isSelected, int row, int column)
1840 currentFilter = (FeatureMatcherSetI) value;
1841 this.rowSelected = row;
1842 type = me.table.getValueAt(row, TYPE_COLUMN).toString();
1843 button.setOpaque(true);
1844 button.setBackground(me.getBackground());
1845 button.setText(currentFilter.toString());
1846 button.setToolTipText(currentFilter.toString());
1847 button.setIcon(null);
1853 class FeatureIcon implements Icon
1855 FeatureColourI gcol;
1859 boolean midspace = false;
1861 int width = 50, height = 20;
1863 int s1, e1; // start and end of midpoint band for thresholded symbol
1865 Color mpcolour = Color.white;
1867 FeatureIcon(FeatureColourI gfc, Color bg, int w, int h, boolean mspace)
1887 public int getIconWidth()
1893 public int getIconHeight()
1899 public void paintIcon(Component c, Graphics g, int x, int y)
1902 if (gcol.isColourByLabel())
1905 g.fillRect(0, 0, width, height);
1906 // need an icon here.
1907 g.setColor(gcol.getMaxColour());
1909 g.setFont(new Font("Verdana", Font.PLAIN, 9));
1911 // g.setFont(g.getFont().deriveFont(
1912 // AffineTransform.getScaleInstance(
1913 // width/g.getFontMetrics().stringWidth("Label"),
1914 // height/g.getFontMetrics().getHeight())));
1916 g.drawString(MessageManager.getString("label.label"), 0, 0);
1921 Color minCol = gcol.getMinColour();
1923 g.fillRect(0, 0, s1, height);
1926 g.setColor(Color.white);
1927 g.fillRect(s1, 0, e1 - s1, height);
1929 g.setColor(gcol.getMaxColour());
1930 g.fillRect(0, e1, width - e1, height);